1// Copyright 2013 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/utility/cloud_print/pwg_encoder.h" 6 7#include <algorithm> 8 9#include "base/big_endian.h" 10#include "base/logging.h" 11#include "base/memory/scoped_ptr.h" 12#include "chrome/utility/cloud_print/bitmap_image.h" 13 14namespace cloud_print { 15 16namespace { 17 18const uint32 kBitsPerColor = 8; 19const uint32 kColorOrder = 0; // chunky. 20 21// Coefficients used to convert from RGB to monochrome. 22const uint32 kRedCoefficient = 2125; 23const uint32 kGreenCoefficient = 7154; 24const uint32 kBlueCoefficient = 721; 25const uint32 kColorCoefficientDenominator = 10000; 26 27const char* kPwgKeyword = "RaS2"; 28 29const uint32 kHeaderSize = 1796; 30const uint32 kHeaderCupsDuplex = 272; 31const uint32 kHeaderCupsHwResolutionHorizontal = 276; 32const uint32 kHeaderCupsHwResolutionVertical = 280; 33const uint32 kHeaderCupsTumble = 368; 34const uint32 kHeaderCupsWidth = 372; 35const uint32 kHeaderCupsHeight = 376; 36const uint32 kHeaderCupsBitsPerColor = 384; 37const uint32 kHeaderCupsBitsPerPixel = 388; 38const uint32 kHeaderCupsBytesPerLine = 392; 39const uint32 kHeaderCupsColorOrder = 396; 40const uint32 kHeaderCupsColorSpace = 400; 41const uint32 kHeaderCupsNumColors = 420; 42const uint32 kHeaderPwgTotalPageCount = 452; 43const uint32 kHeaderPwgCrossFeedTransform = 456; 44const uint32 kHeaderPwgFeedTransform = 460; 45 46const int kPwgMaxPackedRows = 256; 47 48const int kPwgMaxPackedPixels = 128; 49 50struct RGBA8 { 51 uint8 red; 52 uint8 green; 53 uint8 blue; 54 uint8 alpha; 55}; 56 57struct BGRA8 { 58 uint8 blue; 59 uint8 green; 60 uint8 red; 61 uint8 alpha; 62}; 63 64template <class InputStruct> 65inline void encodePixelToRGB(const void* pixel, std::string* output) { 66 const InputStruct* i = reinterpret_cast<const InputStruct*>(pixel); 67 output->push_back(static_cast<char>(i->red)); 68 output->push_back(static_cast<char>(i->green)); 69 output->push_back(static_cast<char>(i->blue)); 70} 71 72template <class InputStruct> 73inline void encodePixelToMonochrome(const void* pixel, std::string* output) { 74 const InputStruct* i = reinterpret_cast<const InputStruct*>(pixel); 75 output->push_back(static_cast<char>((i->red * kRedCoefficient + 76 i->green * kGreenCoefficient + 77 i->blue * kBlueCoefficient) / 78 kColorCoefficientDenominator)); 79} 80 81} // namespace 82 83PwgEncoder::PwgEncoder() {} 84 85void PwgEncoder::EncodeDocumentHeader(std::string* output) const { 86 output->clear(); 87 output->append(kPwgKeyword, 4); 88} 89 90void PwgEncoder::EncodePageHeader(const BitmapImage& image, 91 const PwgHeaderInfo& pwg_header_info, 92 std::string* output) const { 93 char header[kHeaderSize]; 94 memset(header, 0, kHeaderSize); 95 96 uint32 num_colors = 97 pwg_header_info.color_space == PwgHeaderInfo::SGRAY ? 1 : 3; 98 uint32 bits_per_pixel = num_colors * kBitsPerColor; 99 100 base::WriteBigEndian<uint32>(header + kHeaderCupsDuplex, 101 pwg_header_info.duplex ? 1 : 0); 102 base::WriteBigEndian<uint32>(header + kHeaderCupsHwResolutionHorizontal, 103 pwg_header_info.dpi); 104 base::WriteBigEndian<uint32>(header + kHeaderCupsHwResolutionVertical, 105 pwg_header_info.dpi); 106 base::WriteBigEndian<uint32>(header + kHeaderCupsTumble, 107 pwg_header_info.tumble ? 1 : 0); 108 base::WriteBigEndian<uint32>(header + kHeaderCupsWidth, image.size().width()); 109 base::WriteBigEndian<uint32>(header + kHeaderCupsHeight, 110 image.size().height()); 111 base::WriteBigEndian<uint32>(header + kHeaderCupsBitsPerColor, kBitsPerColor); 112 base::WriteBigEndian<uint32>(header + kHeaderCupsBitsPerPixel, 113 bits_per_pixel); 114 base::WriteBigEndian<uint32>(header + kHeaderCupsBytesPerLine, 115 (bits_per_pixel * image.size().width() + 7) / 8); 116 base::WriteBigEndian<uint32>(header + kHeaderCupsColorOrder, kColorOrder); 117 base::WriteBigEndian<uint32>(header + kHeaderCupsColorSpace, 118 pwg_header_info.color_space); 119 base::WriteBigEndian<uint32>(header + kHeaderCupsNumColors, num_colors); 120 base::WriteBigEndian<uint32>(header + kHeaderPwgCrossFeedTransform, 121 pwg_header_info.flipx ? -1 : 1); 122 base::WriteBigEndian<uint32>(header + kHeaderPwgFeedTransform, 123 pwg_header_info.flipy ? -1 : 1); 124 base::WriteBigEndian<uint32>(header + kHeaderPwgTotalPageCount, 125 pwg_header_info.total_pages); 126 output->append(header, kHeaderSize); 127} 128 129template <typename InputStruct, class RandomAccessIterator> 130void PwgEncoder::EncodeRow(RandomAccessIterator pos, 131 RandomAccessIterator row_end, 132 bool monochrome, 133 std::string* output) const { 134 // According to PWG-raster, a sequence of N identical pixels (up to 128) 135 // can be encoded by a byte N-1, followed by the information on 136 // that pixel. Any generic sequence of N pixels (up to 129) can be encoded 137 // with (signed) byte 1-N, followed by the information on the N pixels. 138 // Notice that for sequences of 1 pixel there is no difference between 139 // the two encodings. 140 141 // We encode every largest sequence of identical pixels together because it 142 // usually saves the most space. Every other pixel should be encoded in the 143 // smallest number of generic sequences. 144 // NOTE: the algorithm is not optimal especially in case of monochrome. 145 while (pos != row_end) { 146 RandomAccessIterator it = pos + 1; 147 RandomAccessIterator end = std::min(pos + kPwgMaxPackedPixels, row_end); 148 149 // Counts how many identical pixels (up to 128). 150 while (it != end && *pos == *it) { 151 ++it; 152 } 153 if (it != pos + 1) { // More than one pixel 154 output->push_back(static_cast<char>((it - pos) - 1)); 155 if (monochrome) 156 encodePixelToMonochrome<InputStruct>(&*pos, output); 157 else 158 encodePixelToRGB<InputStruct>(&*pos, output); 159 pos = it; 160 } else { 161 // Finds how many pixels there are each different from the previous one. 162 // IMPORTANT: even if sequences of different pixels can contain as many 163 // as 129 pixels, we restrict to 128 because some decoders don't manage 164 // it correctly. So iterating until it != end is correct. 165 while (it != end && *it != *(it - 1)) { 166 ++it; 167 } 168 // Optimization: ignores the last pixel of the sequence if it is followed 169 // by an identical pixel, as it is more convenient for it to be the start 170 // of a new sequence of identical pixels. Notice that we don't compare 171 // to end, but row_end. 172 if (it != row_end && *it == *(it - 1)) { 173 --it; 174 } 175 output->push_back(static_cast<char>(1 - (it - pos))); 176 while (pos != it) { 177 if (monochrome) 178 encodePixelToMonochrome<InputStruct>(&*pos, output); 179 else 180 encodePixelToRGB<InputStruct>(&*pos, output); 181 ++pos; 182 } 183 } 184 } 185} 186 187inline const uint8* PwgEncoder::GetRow(const BitmapImage& image, 188 int row, 189 bool flipy) const { 190 return image.GetPixel( 191 gfx::Point(0, flipy ? image.size().height() - 1 - row : row)); 192} 193 194// Given a pointer to a struct Image, create a PWG of the image and 195// put the compressed image data in the string. Returns true on success. 196// The content of the string is undefined on failure. 197bool PwgEncoder::EncodePage(const BitmapImage& image, 198 const PwgHeaderInfo& pwg_header_info, 199 std::string* output) const { 200 // pwg_header_info.color_space can only contain color spaces that are 201 // supported, so no sanity check is needed. 202 switch (image.colorspace()) { 203 case BitmapImage::RGBA: 204 return EncodePageWithColorspace<RGBA8>(image, pwg_header_info, output); 205 206 case BitmapImage::BGRA: 207 return EncodePageWithColorspace<BGRA8>(image, pwg_header_info, output); 208 209 default: 210 LOG(ERROR) << "Unsupported colorspace."; 211 return false; 212 } 213} 214 215template <typename InputStruct> 216bool PwgEncoder::EncodePageWithColorspace(const BitmapImage& image, 217 const PwgHeaderInfo& pwg_header_info, 218 std::string* output) const { 219 bool monochrome = pwg_header_info.color_space == PwgHeaderInfo::SGRAY; 220 EncodePageHeader(image, pwg_header_info, output); 221 222 // Ensure no integer overflow. 223 CHECK(image.size().width() < INT_MAX / image.channels()); 224 int row_size = image.size().width() * image.channels(); 225 226 int row_number = 0; 227 while (row_number < image.size().height()) { 228 const uint8* current_row = 229 GetRow(image, row_number++, pwg_header_info.flipy); 230 int num_identical_rows = 1; 231 // We count how many times the current row is repeated. 232 while (num_identical_rows < kPwgMaxPackedRows && 233 row_number < image.size().height() && 234 !memcmp(current_row, 235 GetRow(image, row_number, pwg_header_info.flipy), 236 row_size)) { 237 num_identical_rows++; 238 row_number++; 239 } 240 output->push_back(static_cast<char>(num_identical_rows - 1)); 241 242 // Both supported colorspaces have a 32-bit pixels information. 243 // Converts the list of uint8 to uint32 as every pixels contains 4 bytes 244 // of information and comparison of elements is easier. The actual 245 // Management of the bytes of the pixel is done by pixel_encoder function 246 // on the original array to avoid endian problems. 247 const uint32* pos = reinterpret_cast<const uint32*>(current_row); 248 const uint32* row_end = pos + image.size().width(); 249 if (!pwg_header_info.flipx) { 250 EncodeRow<InputStruct>(pos, row_end, monochrome, output); 251 } else { 252 // We reverse the iterators. 253 EncodeRow<InputStruct>(std::reverse_iterator<const uint32*>(row_end), 254 std::reverse_iterator<const uint32*>(pos), 255 monochrome, 256 output); 257 } 258 } 259 return true; 260} 261 262} // namespace cloud_print 263