| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/utility/cloud_print/pwg_encoder.h" |
| |
| #include <algorithm> |
| |
| #include "base/big_endian.h" |
| #include "base/logging.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "chrome/utility/cloud_print/bitmap_image.h" |
| |
| namespace cloud_print { |
| |
| namespace { |
| |
| const uint32 kBitsPerColor = 8; |
| const uint32 kColorOrder = 0; // chunky. |
| |
| // Coefficients used to convert from RGB to monochrome. |
| const uint32 kRedCoefficient = 2125; |
| const uint32 kGreenCoefficient = 7154; |
| const uint32 kBlueCoefficient = 0721; |
| const uint32 kColorCoefficientDenominator = 10000; |
| |
| const char* kPwgKeyword = "RaS2"; |
| |
| const uint32 kHeaderSize = 1796; |
| const uint32 kHeaderCupsDuplex = 272; |
| const uint32 kHeaderCupsHwResolutionHorizontal = 276; |
| const uint32 kHeaderCupsHwResolutionVertical = 280; |
| const uint32 kHeaderCupsTumble = 368; |
| const uint32 kHeaderCupsWidth = 372; |
| const uint32 kHeaderCupsHeight = 376; |
| const uint32 kHeaderCupsBitsPerColor = 384; |
| const uint32 kHeaderCupsBitsPerPixel = 388; |
| const uint32 kHeaderCupsBytesPerLine = 392; |
| const uint32 kHeaderCupsColorOrder = 396; |
| const uint32 kHeaderCupsColorSpace = 400; |
| const uint32 kHeaderCupsNumColors = 420; |
| const uint32 kHeaderPwgTotalPageCount = 452; |
| const uint32 kHeaderPwgCrossFeedTransform = 456; |
| const uint32 kHeaderPwgFeedTransform = 460; |
| |
| const int kPwgMaxPackedRows = 256; |
| |
| const int kPwgMaxPackedPixels = 128; |
| |
| struct RGBA8 { |
| uint8 red; |
| uint8 green; |
| uint8 blue; |
| uint8 alpha; |
| }; |
| |
| struct BGRA8 { |
| uint8 blue; |
| uint8 green; |
| uint8 red; |
| uint8 alpha; |
| }; |
| |
| template <class InputStruct> |
| inline void encodePixelToRGB(const void* pixel, std::string* output) { |
| const InputStruct* i = reinterpret_cast<const InputStruct*>(pixel); |
| output->push_back(static_cast<char>(i->red)); |
| output->push_back(static_cast<char>(i->green)); |
| output->push_back(static_cast<char>(i->blue)); |
| } |
| |
| template <class InputStruct> |
| inline void encodePixelToMonochrome(const void* pixel, std::string* output) { |
| const InputStruct* i = reinterpret_cast<const InputStruct*>(pixel); |
| output->push_back(static_cast<char>((i->red * kRedCoefficient + |
| i->green * kGreenCoefficient + |
| i->blue * kBlueCoefficient) / |
| kColorCoefficientDenominator)); |
| } |
| |
| } // namespace |
| |
| PwgEncoder::PwgEncoder() {} |
| |
| void PwgEncoder::EncodeDocumentHeader(std::string* output) const { |
| output->clear(); |
| output->append(kPwgKeyword, 4); |
| } |
| |
| void PwgEncoder::EncodePageHeader(const BitmapImage& image, |
| const PwgHeaderInfo& pwg_header_info, |
| std::string* output) const { |
| char header[kHeaderSize]; |
| memset(header, 0, kHeaderSize); |
| |
| uint32 num_colors = |
| pwg_header_info.color_space == PwgHeaderInfo::SGRAY ? 1 : 3; |
| uint32 bits_per_pixel = num_colors * kBitsPerColor; |
| |
| base::WriteBigEndian<uint32>(header + kHeaderCupsDuplex, |
| pwg_header_info.duplex ? 1 : 0); |
| base::WriteBigEndian<uint32>(header + kHeaderCupsHwResolutionHorizontal, |
| pwg_header_info.dpi); |
| base::WriteBigEndian<uint32>(header + kHeaderCupsHwResolutionVertical, |
| pwg_header_info.dpi); |
| base::WriteBigEndian<uint32>(header + kHeaderCupsTumble, |
| pwg_header_info.tumble ? 1 : 0); |
| base::WriteBigEndian<uint32>(header + kHeaderCupsWidth, image.size().width()); |
| base::WriteBigEndian<uint32>(header + kHeaderCupsHeight, |
| image.size().height()); |
| base::WriteBigEndian<uint32>(header + kHeaderCupsBitsPerColor, kBitsPerColor); |
| base::WriteBigEndian<uint32>(header + kHeaderCupsBitsPerPixel, |
| bits_per_pixel); |
| base::WriteBigEndian<uint32>(header + kHeaderCupsBytesPerLine, |
| (bits_per_pixel * image.size().width() + 7) / 8); |
| base::WriteBigEndian<uint32>(header + kHeaderCupsColorOrder, kColorOrder); |
| base::WriteBigEndian<uint32>(header + kHeaderCupsColorSpace, |
| pwg_header_info.color_space); |
| base::WriteBigEndian<uint32>(header + kHeaderCupsNumColors, num_colors); |
| base::WriteBigEndian<uint32>(header + kHeaderPwgCrossFeedTransform, |
| pwg_header_info.flipx ? -1 : 1); |
| base::WriteBigEndian<uint32>(header + kHeaderPwgFeedTransform, |
| pwg_header_info.flipy ? -1 : 1); |
| base::WriteBigEndian<uint32>(header + kHeaderPwgTotalPageCount, |
| pwg_header_info.total_pages); |
| output->append(header, kHeaderSize); |
| } |
| |
| template <typename InputStruct, class RandomAccessIterator> |
| void PwgEncoder::EncodeRow(RandomAccessIterator pos, |
| RandomAccessIterator row_end, |
| bool monochrome, |
| std::string* output) const { |
| // According to PWG-raster, a sequence of N identical pixels (up to 128) |
| // can be encoded by a byte N-1, followed by the information on |
| // that pixel. Any generic sequence of N pixels (up to 129) can be encoded |
| // with (signed) byte 1-N, followed by the information on the N pixels. |
| // Notice that for sequences of 1 pixel there is no difference between |
| // the two encodings. |
| |
| // We encode every largest sequence of identical pixels together because it |
| // usually saves the most space. Every other pixel should be encoded in the |
| // smallest number of generic sequences. |
| // NOTE: the algorithm is not optimal especially in case of monochrome. |
| while (pos != row_end) { |
| RandomAccessIterator it = pos + 1; |
| RandomAccessIterator end = std::min(pos + kPwgMaxPackedPixels, row_end); |
| |
| // Counts how many identical pixels (up to 128). |
| while (it != end && *pos == *it) { |
| ++it; |
| } |
| if (it != pos + 1) { // More than one pixel |
| output->push_back(static_cast<char>((it - pos) - 1)); |
| if (monochrome) |
| encodePixelToMonochrome<InputStruct>(&*pos, output); |
| else |
| encodePixelToRGB<InputStruct>(&*pos, output); |
| pos = it; |
| } else { |
| // Finds how many pixels there are each different from the previous one. |
| // IMPORTANT: even if sequences of different pixels can contain as many |
| // as 129 pixels, we restrict to 128 because some decoders don't manage |
| // it correctly. So iterating until it != end is correct. |
| while (it != end && *it != *(it - 1)) { |
| ++it; |
| } |
| // Optimization: ignores the last pixel of the sequence if it is followed |
| // by an identical pixel, as it is more convenient for it to be the start |
| // of a new sequence of identical pixels. Notice that we don't compare |
| // to end, but row_end. |
| if (it != row_end && *it == *(it - 1)) { |
| --it; |
| } |
| output->push_back(static_cast<char>(1 - (it - pos))); |
| while (pos != it) { |
| if (monochrome) |
| encodePixelToMonochrome<InputStruct>(&*pos, output); |
| else |
| encodePixelToRGB<InputStruct>(&*pos, output); |
| ++pos; |
| } |
| } |
| } |
| } |
| |
| inline const uint8* PwgEncoder::GetRow(const BitmapImage& image, |
| int row, |
| bool flipy) const { |
| return image.GetPixel( |
| gfx::Point(0, flipy ? image.size().height() - 1 - row : row)); |
| } |
| |
| // Given a pointer to a struct Image, create a PWG of the image and |
| // put the compressed image data in the string. Returns true on success. |
| // The content of the string is undefined on failure. |
| bool PwgEncoder::EncodePage(const BitmapImage& image, |
| const PwgHeaderInfo& pwg_header_info, |
| std::string* output) const { |
| // pwg_header_info.color_space can only contain color spaces that are |
| // supported, so no sanity check is needed. |
| switch (image.colorspace()) { |
| case BitmapImage::RGBA: |
| return EncodePageWithColorspace<RGBA8>(image, pwg_header_info, output); |
| |
| case BitmapImage::BGRA: |
| return EncodePageWithColorspace<BGRA8>(image, pwg_header_info, output); |
| |
| default: |
| LOG(ERROR) << "Unsupported colorspace."; |
| return false; |
| } |
| } |
| |
| template <typename InputStruct> |
| bool PwgEncoder::EncodePageWithColorspace(const BitmapImage& image, |
| const PwgHeaderInfo& pwg_header_info, |
| std::string* output) const { |
| bool monochrome = pwg_header_info.color_space == PwgHeaderInfo::SGRAY; |
| EncodePageHeader(image, pwg_header_info, output); |
| |
| // Ensure no integer overflow. |
| CHECK(image.size().width() < INT_MAX / image.channels()); |
| int row_size = image.size().width() * image.channels(); |
| |
| int row_number = 0; |
| while (row_number < image.size().height()) { |
| const uint8* current_row = |
| GetRow(image, row_number++, pwg_header_info.flipy); |
| int num_identical_rows = 1; |
| // We count how many times the current row is repeated. |
| while (num_identical_rows < kPwgMaxPackedRows && |
| row_number < image.size().height() && |
| !memcmp(current_row, |
| GetRow(image, row_number, pwg_header_info.flipy), |
| row_size)) { |
| num_identical_rows++; |
| row_number++; |
| } |
| output->push_back(static_cast<char>(num_identical_rows - 1)); |
| |
| // Both supported colorspaces have a 32-bit pixels information. |
| // Converts the list of uint8 to uint32 as every pixels contains 4 bytes |
| // of information and comparison of elements is easier. The actual |
| // Management of the bytes of the pixel is done by pixel_encoder function |
| // on the original array to avoid endian problems. |
| const uint32* pos = reinterpret_cast<const uint32*>(current_row); |
| const uint32* row_end = pos + image.size().width(); |
| if (!pwg_header_info.flipx) { |
| EncodeRow<InputStruct>(pos, row_end, monochrome, output); |
| } else { |
| // We reverse the iterators. |
| EncodeRow<InputStruct>(std::reverse_iterator<const uint32*>(row_end), |
| std::reverse_iterator<const uint32*>(pos), |
| monochrome, |
| output); |
| } |
| } |
| return true; |
| } |
| |
| } // namespace cloud_print |