1 | /* |
2 | * Copyright 2015 Google Inc. |
3 | * |
4 | * Use of this source code is governed by a BSD-style license that can be |
5 | * found in the LICENSE file. |
6 | */ |
7 | |
8 | #include "include/core/SkStream.h" |
9 | #include "include/private/SkColorData.h" |
10 | #include "src/codec/SkBmpStandardCodec.h" |
11 | #include "src/codec/SkCodecPriv.h" |
12 | #include "src/core/SkMathPriv.h" |
13 | |
14 | /* |
15 | * Creates an instance of the decoder |
16 | * Called only by NewFromStream |
17 | */ |
18 | SkBmpStandardCodec::SkBmpStandardCodec(SkEncodedInfo&& info, std::unique_ptr<SkStream> stream, |
19 | uint16_t bitsPerPixel, uint32_t numColors, |
20 | uint32_t bytesPerColor, uint32_t offset, |
21 | SkCodec::SkScanlineOrder rowOrder, |
22 | bool isOpaque, bool inIco) |
23 | : INHERITED(std::move(info), std::move(stream), bitsPerPixel, rowOrder) |
24 | , fColorTable(nullptr) |
25 | , fNumColors(numColors) |
26 | , fBytesPerColor(bytesPerColor) |
27 | , fOffset(offset) |
28 | , fSwizzler(nullptr) |
29 | , fIsOpaque(isOpaque) |
30 | , fInIco(inIco) |
31 | , fAndMaskRowBytes(fInIco ? SkAlign4(compute_row_bytes(this->dimensions().width(), 1)) : 0) |
32 | {} |
33 | |
34 | /* |
35 | * Initiates the bitmap decode |
36 | */ |
37 | SkCodec::Result SkBmpStandardCodec::onGetPixels(const SkImageInfo& dstInfo, |
38 | void* dst, size_t dstRowBytes, |
39 | const Options& opts, |
40 | int* rowsDecoded) { |
41 | if (opts.fSubset) { |
42 | // Subsets are not supported. |
43 | return kUnimplemented; |
44 | } |
45 | if (dstInfo.dimensions() != this->dimensions()) { |
46 | SkCodecPrintf("Error: scaling not supported.\n" ); |
47 | return kInvalidScale; |
48 | } |
49 | |
50 | Result result = this->prepareToDecode(dstInfo, opts); |
51 | if (kSuccess != result) { |
52 | return result; |
53 | } |
54 | int rows = this->decodeRows(dstInfo, dst, dstRowBytes, opts); |
55 | if (rows != dstInfo.height()) { |
56 | *rowsDecoded = rows; |
57 | return kIncompleteInput; |
58 | } |
59 | return kSuccess; |
60 | } |
61 | |
62 | /* |
63 | * Process the color table for the bmp input |
64 | */ |
65 | bool SkBmpStandardCodec::createColorTable(SkColorType dstColorType, SkAlphaType dstAlphaType) { |
66 | // Allocate memory for color table |
67 | uint32_t colorBytes = 0; |
68 | SkPMColor colorTable[256]; |
69 | if (this->bitsPerPixel() <= 8) { |
70 | // Inform the caller of the number of colors |
71 | uint32_t maxColors = 1 << this->bitsPerPixel(); |
72 | // Don't bother reading more than maxColors. |
73 | const uint32_t numColorsToRead = |
74 | fNumColors == 0 ? maxColors : std::min(fNumColors, maxColors); |
75 | |
76 | // Read the color table from the stream |
77 | colorBytes = numColorsToRead * fBytesPerColor; |
78 | std::unique_ptr<uint8_t[]> cBuffer(new uint8_t[colorBytes]); |
79 | if (stream()->read(cBuffer.get(), colorBytes) != colorBytes) { |
80 | SkCodecPrintf("Error: unable to read color table.\n" ); |
81 | return false; |
82 | } |
83 | |
84 | SkColorType packColorType = dstColorType; |
85 | SkAlphaType packAlphaType = dstAlphaType; |
86 | if (this->colorXform()) { |
87 | packColorType = kBGRA_8888_SkColorType; |
88 | packAlphaType = kUnpremul_SkAlphaType; |
89 | } |
90 | |
91 | // Choose the proper packing function |
92 | bool isPremul = (kPremul_SkAlphaType == packAlphaType) && !fIsOpaque; |
93 | PackColorProc packARGB = choose_pack_color_proc(isPremul, packColorType); |
94 | |
95 | // Fill in the color table |
96 | uint32_t i = 0; |
97 | for (; i < numColorsToRead; i++) { |
98 | uint8_t blue = get_byte(cBuffer.get(), i*fBytesPerColor); |
99 | uint8_t green = get_byte(cBuffer.get(), i*fBytesPerColor + 1); |
100 | uint8_t red = get_byte(cBuffer.get(), i*fBytesPerColor + 2); |
101 | uint8_t alpha; |
102 | if (fIsOpaque) { |
103 | alpha = 0xFF; |
104 | } else { |
105 | alpha = get_byte(cBuffer.get(), i*fBytesPerColor + 3); |
106 | } |
107 | colorTable[i] = packARGB(alpha, red, green, blue); |
108 | } |
109 | |
110 | // To avoid segmentation faults on bad pixel data, fill the end of the |
111 | // color table with black. This is the same the behavior as the |
112 | // chromium decoder. |
113 | for (; i < maxColors; i++) { |
114 | colorTable[i] = SkPackARGB32NoCheck(0xFF, 0, 0, 0); |
115 | } |
116 | |
117 | if (this->colorXform() && !this->xformOnDecode()) { |
118 | this->applyColorXform(colorTable, colorTable, maxColors); |
119 | } |
120 | |
121 | // Set the color table |
122 | fColorTable.reset(new SkColorTable(colorTable, maxColors)); |
123 | } |
124 | |
125 | // Bmp-in-Ico files do not use an offset to indicate where the pixel data |
126 | // begins. Pixel data always begins immediately after the color table. |
127 | if (!fInIco) { |
128 | // Check that we have not read past the pixel array offset |
129 | if(fOffset < colorBytes) { |
130 | // This may occur on OS 2.1 and other old versions where the color |
131 | // table defaults to max size, and the bmp tries to use a smaller |
132 | // color table. This is invalid, and our decision is to indicate |
133 | // an error, rather than try to guess the intended size of the |
134 | // color table. |
135 | SkCodecPrintf("Error: pixel data offset less than color table size.\n" ); |
136 | return false; |
137 | } |
138 | |
139 | // After reading the color table, skip to the start of the pixel array |
140 | if (stream()->skip(fOffset - colorBytes) != fOffset - colorBytes) { |
141 | SkCodecPrintf("Error: unable to skip to image data.\n" ); |
142 | return false; |
143 | } |
144 | } |
145 | |
146 | // Return true on success |
147 | return true; |
148 | } |
149 | |
150 | static SkEncodedInfo make_info(SkEncodedInfo::Color color, |
151 | SkEncodedInfo::Alpha alpha, int bitsPerPixel) { |
152 | // This is just used for the swizzler, which does not need the width or height. |
153 | return SkEncodedInfo::Make(0, 0, color, alpha, bitsPerPixel); |
154 | } |
155 | |
156 | SkEncodedInfo SkBmpStandardCodec::swizzlerInfo() const { |
157 | const auto& info = this->getEncodedInfo(); |
158 | if (fInIco) { |
159 | if (this->bitsPerPixel() <= 8) { |
160 | return make_info(SkEncodedInfo::kPalette_Color, |
161 | info.alpha(), this->bitsPerPixel()); |
162 | } |
163 | if (this->bitsPerPixel() == 24) { |
164 | return make_info(SkEncodedInfo::kBGR_Color, |
165 | SkEncodedInfo::kOpaque_Alpha, 8); |
166 | } |
167 | } |
168 | |
169 | return make_info(info.color(), info.alpha(), info.bitsPerComponent()); |
170 | } |
171 | |
172 | void SkBmpStandardCodec::initializeSwizzler(const SkImageInfo& dstInfo, const Options& opts) { |
173 | // In the case of bmp-in-icos, we will report BGRA to the client, |
174 | // since we may be required to apply an alpha mask after the decode. |
175 | // However, the swizzler needs to know the actual format of the bmp. |
176 | SkEncodedInfo encodedInfo = this->swizzlerInfo(); |
177 | |
178 | // Get a pointer to the color table if it exists |
179 | const SkPMColor* colorPtr = get_color_ptr(fColorTable.get()); |
180 | |
181 | SkImageInfo swizzlerInfo = dstInfo; |
182 | SkCodec::Options swizzlerOptions = opts; |
183 | if (this->colorXform()) { |
184 | swizzlerInfo = swizzlerInfo.makeColorType(kXformSrcColorType); |
185 | if (kPremul_SkAlphaType == dstInfo.alphaType()) { |
186 | swizzlerInfo = swizzlerInfo.makeAlphaType(kUnpremul_SkAlphaType); |
187 | } |
188 | |
189 | swizzlerOptions.fZeroInitialized = kNo_ZeroInitialized; |
190 | } |
191 | |
192 | fSwizzler = SkSwizzler::Make(encodedInfo, colorPtr, swizzlerInfo, swizzlerOptions); |
193 | SkASSERT(fSwizzler); |
194 | } |
195 | |
196 | SkCodec::Result SkBmpStandardCodec::onPrepareToDecode(const SkImageInfo& dstInfo, |
197 | const SkCodec::Options& options) { |
198 | if (this->xformOnDecode()) { |
199 | this->resetXformBuffer(dstInfo.width()); |
200 | } |
201 | |
202 | // Create the color table if necessary and prepare the stream for decode |
203 | // Note that if it is non-NULL, inputColorCount will be modified |
204 | if (!this->createColorTable(dstInfo.colorType(), dstInfo.alphaType())) { |
205 | SkCodecPrintf("Error: could not create color table.\n" ); |
206 | return SkCodec::kInvalidInput; |
207 | } |
208 | |
209 | // Initialize a swizzler |
210 | this->initializeSwizzler(dstInfo, options); |
211 | return SkCodec::kSuccess; |
212 | } |
213 | |
214 | /* |
215 | * Performs the bitmap decoding for standard input format |
216 | */ |
217 | int SkBmpStandardCodec::decodeRows(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, |
218 | const Options& opts) { |
219 | // Iterate over rows of the image |
220 | const int height = dstInfo.height(); |
221 | for (int y = 0; y < height; y++) { |
222 | // Read a row of the input |
223 | if (this->stream()->read(this->srcBuffer(), this->srcRowBytes()) != this->srcRowBytes()) { |
224 | SkCodecPrintf("Warning: incomplete input stream.\n" ); |
225 | return y; |
226 | } |
227 | |
228 | // Decode the row in destination format |
229 | uint32_t row = this->getDstRow(y, dstInfo.height()); |
230 | |
231 | void* dstRow = SkTAddOffset<void>(dst, row * dstRowBytes); |
232 | |
233 | if (this->xformOnDecode()) { |
234 | SkASSERT(this->colorXform()); |
235 | fSwizzler->swizzle(this->xformBuffer(), this->srcBuffer()); |
236 | this->applyColorXform(dstRow, this->xformBuffer(), fSwizzler->swizzleWidth()); |
237 | } else { |
238 | fSwizzler->swizzle(dstRow, this->srcBuffer()); |
239 | } |
240 | } |
241 | |
242 | if (fInIco && fIsOpaque) { |
243 | const int startScanline = this->currScanline(); |
244 | if (startScanline < 0) { |
245 | // We are not performing a scanline decode. |
246 | // Just decode the entire ICO mask and return. |
247 | decodeIcoMask(this->stream(), dstInfo, dst, dstRowBytes); |
248 | return height; |
249 | } |
250 | |
251 | // In order to perform a scanline ICO decode, we must be able |
252 | // to skip ahead in the stream in order to apply the AND mask |
253 | // to the requested scanlines. |
254 | // We will do this by taking advantage of the fact that |
255 | // SkIcoCodec always uses a SkMemoryStream as its underlying |
256 | // representation of the stream. |
257 | const void* memoryBase = this->stream()->getMemoryBase(); |
258 | SkASSERT(nullptr != memoryBase); |
259 | SkASSERT(this->stream()->hasLength()); |
260 | SkASSERT(this->stream()->hasPosition()); |
261 | |
262 | const size_t length = this->stream()->getLength(); |
263 | const size_t currPosition = this->stream()->getPosition(); |
264 | |
265 | // Calculate how many bytes we must skip to reach the AND mask. |
266 | const int remainingScanlines = this->dimensions().height() - startScanline - height; |
267 | const size_t bytesToSkip = remainingScanlines * this->srcRowBytes() + |
268 | startScanline * fAndMaskRowBytes; |
269 | const size_t subStreamStartPosition = currPosition + bytesToSkip; |
270 | if (subStreamStartPosition >= length) { |
271 | // FIXME: How can we indicate that this decode was actually incomplete? |
272 | return height; |
273 | } |
274 | |
275 | // Create a subStream to pass to decodeIcoMask(). It is useful to encapsulate |
276 | // the memory base into a stream in order to safely handle incomplete images |
277 | // without reading out of bounds memory. |
278 | const void* subStreamMemoryBase = SkTAddOffset<const void>(memoryBase, |
279 | subStreamStartPosition); |
280 | const size_t subStreamLength = length - subStreamStartPosition; |
281 | // This call does not transfer ownership of the subStreamMemoryBase. |
282 | SkMemoryStream subStream(subStreamMemoryBase, subStreamLength, false); |
283 | |
284 | // FIXME: If decodeIcoMask does not succeed, is there a way that we can |
285 | // indicate the decode was incomplete? |
286 | decodeIcoMask(&subStream, dstInfo, dst, dstRowBytes); |
287 | } |
288 | |
289 | return height; |
290 | } |
291 | |
292 | void SkBmpStandardCodec::decodeIcoMask(SkStream* stream, const SkImageInfo& dstInfo, |
293 | void* dst, size_t dstRowBytes) { |
294 | // BMP in ICO have transparency, so this cannot be 565. The below code depends |
295 | // on the output being an SkPMColor. |
296 | SkASSERT(kRGBA_8888_SkColorType == dstInfo.colorType() || |
297 | kBGRA_8888_SkColorType == dstInfo.colorType() || |
298 | kRGBA_F16_SkColorType == dstInfo.colorType()); |
299 | |
300 | // If we are sampling, make sure that we only mask the sampled pixels. |
301 | // We do not need to worry about sampling in the y-dimension because that |
302 | // should be handled by SkSampledCodec. |
303 | const int sampleX = fSwizzler->sampleX(); |
304 | const int sampledWidth = get_scaled_dimension(this->dimensions().width(), sampleX); |
305 | const int srcStartX = get_start_coord(sampleX); |
306 | |
307 | |
308 | SkPMColor* dstPtr = (SkPMColor*) dst; |
309 | for (int y = 0; y < dstInfo.height(); y++) { |
310 | // The srcBuffer will at least be large enough |
311 | if (stream->read(this->srcBuffer(), fAndMaskRowBytes) != fAndMaskRowBytes) { |
312 | SkCodecPrintf("Warning: incomplete AND mask for bmp-in-ico.\n" ); |
313 | return; |
314 | } |
315 | |
316 | auto applyMask = [dstInfo](void* dstRow, int x, uint64_t bit) { |
317 | if (kRGBA_F16_SkColorType == dstInfo.colorType()) { |
318 | uint64_t* dst64 = (uint64_t*) dstRow; |
319 | dst64[x] &= bit - 1; |
320 | } else { |
321 | uint32_t* dst32 = (uint32_t*) dstRow; |
322 | dst32[x] &= bit - 1; |
323 | } |
324 | }; |
325 | |
326 | int row = this->getDstRow(y, dstInfo.height()); |
327 | |
328 | void* dstRow = SkTAddOffset<SkPMColor>(dstPtr, row * dstRowBytes); |
329 | |
330 | int srcX = srcStartX; |
331 | for (int dstX = 0; dstX < sampledWidth; dstX++) { |
332 | int quotient; |
333 | int modulus; |
334 | SkTDivMod(srcX, 8, "ient, &modulus); |
335 | uint32_t shift = 7 - modulus; |
336 | uint64_t alphaBit = (this->srcBuffer()[quotient] >> shift) & 0x1; |
337 | applyMask(dstRow, dstX, alphaBit); |
338 | srcX += sampleX; |
339 | } |
340 | } |
341 | } |
342 | |