// 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