1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 #include "ImageLogging.h"
7 #include "nsCRT.h"
8 #include "nsPNGEncoder.h"
9 #include "nsStreamUtils.h"
10 #include "nsString.h"
11 #include "prprf.h"
12 #include "mozilla/CheckedInt.h"
13
14 using namespace mozilla;
15
16 static LazyLogModule sPNGEncoderLog("PNGEncoder");
17
NS_IMPL_ISUPPORTS(nsPNGEncoder,imgIEncoder,nsIInputStream,nsIAsyncInputStream)18 NS_IMPL_ISUPPORTS(nsPNGEncoder, imgIEncoder, nsIInputStream,
19 nsIAsyncInputStream)
20
21 nsPNGEncoder::nsPNGEncoder()
22 : mPNG(nullptr),
23 mPNGinfo(nullptr),
24 mIsAnimation(false),
25 mFinished(false),
26 mImageBuffer(nullptr),
27 mImageBufferSize(0),
28 mImageBufferUsed(0),
29 mImageBufferReadPoint(0),
30 mCallback(nullptr),
31 mCallbackTarget(nullptr),
32 mNotifyThreshold(0),
33 mReentrantMonitor("nsPNGEncoder.mReentrantMonitor") {}
34
~nsPNGEncoder()35 nsPNGEncoder::~nsPNGEncoder() {
36 if (mImageBuffer) {
37 free(mImageBuffer);
38 mImageBuffer = nullptr;
39 }
40 // don't leak if EndImageEncode wasn't called
41 if (mPNG) {
42 png_destroy_write_struct(&mPNG, &mPNGinfo);
43 }
44 }
45
46 // nsPNGEncoder::InitFromData
47 //
48 // One output option is supported: "transparency=none" means that the
49 // output PNG will not have an alpha channel, even if the input does.
50 //
51 // Based partially on gfx/cairo/cairo/src/cairo-png.c
52 // See also media/libpng/libpng-manual.txt
53
54 NS_IMETHODIMP
InitFromData(const uint8_t * aData,uint32_t aLength,uint32_t aWidth,uint32_t aHeight,uint32_t aStride,uint32_t aInputFormat,const nsAString & aOutputOptions)55 nsPNGEncoder::InitFromData(const uint8_t* aData,
56 uint32_t aLength, // (unused, req'd by JS)
57 uint32_t aWidth, uint32_t aHeight, uint32_t aStride,
58 uint32_t aInputFormat,
59 const nsAString& aOutputOptions) {
60 NS_ENSURE_ARG(aData);
61 nsresult rv;
62
63 rv = StartImageEncode(aWidth, aHeight, aInputFormat, aOutputOptions);
64 if (!NS_SUCCEEDED(rv)) {
65 return rv;
66 }
67
68 rv = AddImageFrame(aData, aLength, aWidth, aHeight, aStride, aInputFormat,
69 aOutputOptions);
70 if (!NS_SUCCEEDED(rv)) {
71 return rv;
72 }
73
74 rv = EndImageEncode();
75
76 return rv;
77 }
78
79 // nsPNGEncoder::StartImageEncode
80 //
81 //
82 // See ::InitFromData for other info.
83 NS_IMETHODIMP
StartImageEncode(uint32_t aWidth,uint32_t aHeight,uint32_t aInputFormat,const nsAString & aOutputOptions)84 nsPNGEncoder::StartImageEncode(uint32_t aWidth, uint32_t aHeight,
85 uint32_t aInputFormat,
86 const nsAString& aOutputOptions) {
87 bool useTransparency = true, skipFirstFrame = false;
88 uint32_t numFrames = 1;
89 uint32_t numPlays = 0; // For animations, 0 == forever
90
91 // can't initialize more than once
92 if (mImageBuffer != nullptr) {
93 return NS_ERROR_ALREADY_INITIALIZED;
94 }
95
96 // validate input format
97 if (aInputFormat != INPUT_FORMAT_RGB && aInputFormat != INPUT_FORMAT_RGBA &&
98 aInputFormat != INPUT_FORMAT_HOSTARGB)
99 return NS_ERROR_INVALID_ARG;
100
101 // parse and check any provided output options
102 nsresult rv = ParseOptions(aOutputOptions, &useTransparency, &skipFirstFrame,
103 &numFrames, &numPlays, nullptr, nullptr, nullptr,
104 nullptr, nullptr);
105 if (rv != NS_OK) {
106 return rv;
107 }
108
109 #ifdef PNG_APNG_SUPPORTED
110 if (numFrames > 1) {
111 mIsAnimation = true;
112 }
113
114 #endif
115
116 // initialize
117 mPNG = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, ErrorCallback,
118 WarningCallback);
119 if (!mPNG) {
120 return NS_ERROR_OUT_OF_MEMORY;
121 }
122
123 mPNGinfo = png_create_info_struct(mPNG);
124 if (!mPNGinfo) {
125 png_destroy_write_struct(&mPNG, nullptr);
126 return NS_ERROR_FAILURE;
127 }
128
129 // libpng's error handler jumps back here upon an error.
130 // Note: It's important that all png_* callers do this, or errors
131 // will result in a corrupt time-warped stack.
132 if (setjmp(png_jmpbuf(mPNG))) {
133 png_destroy_write_struct(&mPNG, &mPNGinfo);
134 return NS_ERROR_FAILURE;
135 }
136
137 // Set up to read the data into our image buffer, start out with an 8K
138 // estimated size. Note: we don't have to worry about freeing this data
139 // in this function. It will be freed on object destruction.
140 mImageBufferSize = 8192;
141 mImageBuffer = (uint8_t*)malloc(mImageBufferSize);
142 if (!mImageBuffer) {
143 png_destroy_write_struct(&mPNG, &mPNGinfo);
144 return NS_ERROR_OUT_OF_MEMORY;
145 }
146 mImageBufferUsed = 0;
147
148 // set our callback for libpng to give us the data
149 png_set_write_fn(mPNG, this, WriteCallback, nullptr);
150
151 // include alpha?
152 int colorType;
153 if ((aInputFormat == INPUT_FORMAT_HOSTARGB ||
154 aInputFormat == INPUT_FORMAT_RGBA) &&
155 useTransparency)
156 colorType = PNG_COLOR_TYPE_RGB_ALPHA;
157 else
158 colorType = PNG_COLOR_TYPE_RGB;
159
160 png_set_IHDR(mPNG, mPNGinfo, aWidth, aHeight, 8, colorType,
161 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
162 PNG_FILTER_TYPE_DEFAULT);
163
164 #ifdef PNG_APNG_SUPPORTED
165 if (mIsAnimation) {
166 png_set_first_frame_is_hidden(mPNG, mPNGinfo, skipFirstFrame);
167 png_set_acTL(mPNG, mPNGinfo, numFrames, numPlays);
168 }
169 #endif
170
171 // XXX: support PLTE, gAMA, tRNS, bKGD?
172
173 png_write_info(mPNG, mPNGinfo);
174
175 return NS_OK;
176 }
177
178 // Returns the number of bytes in the image buffer used.
179 NS_IMETHODIMP
GetImageBufferUsed(uint32_t * aOutputSize)180 nsPNGEncoder::GetImageBufferUsed(uint32_t* aOutputSize) {
181 NS_ENSURE_ARG_POINTER(aOutputSize);
182 *aOutputSize = mImageBufferUsed;
183 return NS_OK;
184 }
185
186 // Returns a pointer to the start of the image buffer
187 NS_IMETHODIMP
GetImageBuffer(char ** aOutputBuffer)188 nsPNGEncoder::GetImageBuffer(char** aOutputBuffer) {
189 NS_ENSURE_ARG_POINTER(aOutputBuffer);
190 *aOutputBuffer = reinterpret_cast<char*>(mImageBuffer);
191 return NS_OK;
192 }
193
194 NS_IMETHODIMP
AddImageFrame(const uint8_t * aData,uint32_t aLength,uint32_t aWidth,uint32_t aHeight,uint32_t aStride,uint32_t aInputFormat,const nsAString & aFrameOptions)195 nsPNGEncoder::AddImageFrame(const uint8_t* aData,
196 uint32_t aLength, // (unused, req'd by JS)
197 uint32_t aWidth, uint32_t aHeight, uint32_t aStride,
198 uint32_t aInputFormat,
199 const nsAString& aFrameOptions) {
200 bool useTransparency = true;
201 uint32_t delay_ms = 500;
202 #ifdef PNG_APNG_SUPPORTED
203 uint32_t dispose_op = PNG_DISPOSE_OP_NONE;
204 uint32_t blend_op = PNG_BLEND_OP_SOURCE;
205 #else
206 uint32_t dispose_op;
207 uint32_t blend_op;
208 #endif
209 uint32_t x_offset = 0, y_offset = 0;
210
211 // must be initialized
212 if (mImageBuffer == nullptr) {
213 return NS_ERROR_NOT_INITIALIZED;
214 }
215
216 // EndImageEncode was done, or some error occurred earlier
217 if (!mPNG) {
218 return NS_BASE_STREAM_CLOSED;
219 }
220
221 // validate input format
222 if (aInputFormat != INPUT_FORMAT_RGB && aInputFormat != INPUT_FORMAT_RGBA &&
223 aInputFormat != INPUT_FORMAT_HOSTARGB)
224 return NS_ERROR_INVALID_ARG;
225
226 // libpng's error handler jumps back here upon an error.
227 if (setjmp(png_jmpbuf(mPNG))) {
228 png_destroy_write_struct(&mPNG, &mPNGinfo);
229 return NS_ERROR_FAILURE;
230 }
231
232 // parse and check any provided output options
233 nsresult rv =
234 ParseOptions(aFrameOptions, &useTransparency, nullptr, nullptr, nullptr,
235 &dispose_op, &blend_op, &delay_ms, &x_offset, &y_offset);
236 if (rv != NS_OK) {
237 return rv;
238 }
239
240 #ifdef PNG_APNG_SUPPORTED
241 if (mIsAnimation) {
242 // XXX the row pointers arg (#3) is unused, can it be removed?
243 png_write_frame_head(mPNG, mPNGinfo, nullptr, aWidth, aHeight, x_offset,
244 y_offset, delay_ms, 1000, dispose_op, blend_op);
245 }
246 #endif
247
248 // Stride is the padded width of each row, so it better be longer
249 // (I'm afraid people will not understand what stride means, so
250 // check it well)
251 if ((aInputFormat == INPUT_FORMAT_RGB && aStride < aWidth * 3) ||
252 ((aInputFormat == INPUT_FORMAT_RGBA ||
253 aInputFormat == INPUT_FORMAT_HOSTARGB) &&
254 aStride < aWidth * 4)) {
255 NS_WARNING("Invalid stride for InitFromData/AddImageFrame");
256 return NS_ERROR_INVALID_ARG;
257 }
258
259 #ifdef PNG_WRITE_FILTER_SUPPORTED
260 png_set_filter(mPNG, PNG_FILTER_TYPE_BASE, PNG_FILTER_VALUE_NONE);
261 #endif
262
263 // write each row: if we add more input formats, we may want to
264 // generalize the conversions
265 if (aInputFormat == INPUT_FORMAT_HOSTARGB) {
266 // PNG requires RGBA with post-multiplied alpha, so we need to
267 // convert
268 UniquePtr<uint8_t[]> row = MakeUnique<uint8_t[]>(aWidth * 4);
269 for (uint32_t y = 0; y < aHeight; y++) {
270 ConvertHostARGBRow(&aData[y * aStride], row.get(), aWidth,
271 useTransparency);
272 png_write_row(mPNG, row.get());
273 }
274 } else if (aInputFormat == INPUT_FORMAT_RGBA && !useTransparency) {
275 // RBGA, but we need to strip the alpha
276 UniquePtr<uint8_t[]> row = MakeUnique<uint8_t[]>(aWidth * 4);
277 for (uint32_t y = 0; y < aHeight; y++) {
278 StripAlpha(&aData[y * aStride], row.get(), aWidth);
279 png_write_row(mPNG, row.get());
280 }
281 } else if (aInputFormat == INPUT_FORMAT_RGB ||
282 aInputFormat == INPUT_FORMAT_RGBA) {
283 // simple RBG(A), no conversion needed
284 for (uint32_t y = 0; y < aHeight; y++) {
285 png_write_row(mPNG, (uint8_t*)&aData[y * aStride]);
286 }
287
288 } else {
289 MOZ_ASSERT_UNREACHABLE("Bad format type");
290 return NS_ERROR_INVALID_ARG;
291 }
292
293 #ifdef PNG_APNG_SUPPORTED
294 if (mIsAnimation) {
295 png_write_frame_tail(mPNG, mPNGinfo);
296 }
297 #endif
298
299 return NS_OK;
300 }
301
302 NS_IMETHODIMP
EndImageEncode()303 nsPNGEncoder::EndImageEncode() {
304 // must be initialized
305 if (mImageBuffer == nullptr) {
306 return NS_ERROR_NOT_INITIALIZED;
307 }
308
309 // EndImageEncode has already been called, or some error
310 // occurred earlier
311 if (!mPNG) {
312 return NS_BASE_STREAM_CLOSED;
313 }
314
315 // libpng's error handler jumps back here upon an error.
316 if (setjmp(png_jmpbuf(mPNG))) {
317 png_destroy_write_struct(&mPNG, &mPNGinfo);
318 return NS_ERROR_FAILURE;
319 }
320
321 png_write_end(mPNG, mPNGinfo);
322 png_destroy_write_struct(&mPNG, &mPNGinfo);
323
324 mFinished = true;
325 NotifyListener();
326
327 // if output callback can't get enough memory, it will free our buffer
328 if (!mImageBuffer) {
329 return NS_ERROR_OUT_OF_MEMORY;
330 }
331
332 return NS_OK;
333 }
334
ParseOptions(const nsAString & aOptions,bool * useTransparency,bool * skipFirstFrame,uint32_t * numFrames,uint32_t * numPlays,uint32_t * frameDispose,uint32_t * frameBlend,uint32_t * frameDelay,uint32_t * offsetX,uint32_t * offsetY)335 nsresult nsPNGEncoder::ParseOptions(const nsAString& aOptions,
336 bool* useTransparency, bool* skipFirstFrame,
337 uint32_t* numFrames, uint32_t* numPlays,
338 uint32_t* frameDispose,
339 uint32_t* frameBlend, uint32_t* frameDelay,
340 uint32_t* offsetX, uint32_t* offsetY) {
341 #ifdef PNG_APNG_SUPPORTED
342 // Make a copy of aOptions, because strtok() will modify it.
343 nsAutoCString optionsCopy;
344 optionsCopy.Assign(NS_ConvertUTF16toUTF8(aOptions));
345 char* options = optionsCopy.BeginWriting();
346
347 while (char* token = nsCRT::strtok(options, ";", &options)) {
348 // If there's an '=' character, split the token around it.
349 char* equals = token;
350 char* value = nullptr;
351 while (*equals != '=' && *equals) {
352 ++equals;
353 }
354 if (*equals == '=') {
355 value = equals + 1;
356 }
357
358 if (value) {
359 *equals = '\0'; // temporary null
360 }
361
362 // transparency=[yes|no|none]
363 if (nsCRT::strcmp(token, "transparency") == 0 && useTransparency) {
364 if (!value) {
365 return NS_ERROR_INVALID_ARG;
366 }
367
368 if (nsCRT::strcmp(value, "none") == 0 ||
369 nsCRT::strcmp(value, "no") == 0) {
370 *useTransparency = false;
371 } else if (nsCRT::strcmp(value, "yes") == 0) {
372 *useTransparency = true;
373 } else {
374 return NS_ERROR_INVALID_ARG;
375 }
376
377 // skipfirstframe=[yes|no]
378 } else if (nsCRT::strcmp(token, "skipfirstframe") == 0 && skipFirstFrame) {
379 if (!value) {
380 return NS_ERROR_INVALID_ARG;
381 }
382
383 if (nsCRT::strcmp(value, "no") == 0) {
384 *skipFirstFrame = false;
385 } else if (nsCRT::strcmp(value, "yes") == 0) {
386 *skipFirstFrame = true;
387 } else {
388 return NS_ERROR_INVALID_ARG;
389 }
390
391 // frames=#
392 } else if (nsCRT::strcmp(token, "frames") == 0 && numFrames) {
393 if (!value) {
394 return NS_ERROR_INVALID_ARG;
395 }
396
397 if (PR_sscanf(value, "%u", numFrames) != 1) {
398 return NS_ERROR_INVALID_ARG;
399 }
400
401 // frames=0 is nonsense.
402 if (*numFrames == 0) {
403 return NS_ERROR_INVALID_ARG;
404 }
405
406 // plays=#
407 } else if (nsCRT::strcmp(token, "plays") == 0 && numPlays) {
408 if (!value) {
409 return NS_ERROR_INVALID_ARG;
410 }
411
412 // plays=0 to loop forever, otherwise play sequence specified
413 // number of times
414 if (PR_sscanf(value, "%u", numPlays) != 1) {
415 return NS_ERROR_INVALID_ARG;
416 }
417
418 // dispose=[none|background|previous]
419 } else if (nsCRT::strcmp(token, "dispose") == 0 && frameDispose) {
420 if (!value) {
421 return NS_ERROR_INVALID_ARG;
422 }
423
424 if (nsCRT::strcmp(value, "none") == 0) {
425 *frameDispose = PNG_DISPOSE_OP_NONE;
426 } else if (nsCRT::strcmp(value, "background") == 0) {
427 *frameDispose = PNG_DISPOSE_OP_BACKGROUND;
428 } else if (nsCRT::strcmp(value, "previous") == 0) {
429 *frameDispose = PNG_DISPOSE_OP_PREVIOUS;
430 } else {
431 return NS_ERROR_INVALID_ARG;
432 }
433
434 // blend=[source|over]
435 } else if (nsCRT::strcmp(token, "blend") == 0 && frameBlend) {
436 if (!value) {
437 return NS_ERROR_INVALID_ARG;
438 }
439
440 if (nsCRT::strcmp(value, "source") == 0) {
441 *frameBlend = PNG_BLEND_OP_SOURCE;
442 } else if (nsCRT::strcmp(value, "over") == 0) {
443 *frameBlend = PNG_BLEND_OP_OVER;
444 } else {
445 return NS_ERROR_INVALID_ARG;
446 }
447
448 // delay=# (in ms)
449 } else if (nsCRT::strcmp(token, "delay") == 0 && frameDelay) {
450 if (!value) {
451 return NS_ERROR_INVALID_ARG;
452 }
453
454 if (PR_sscanf(value, "%u", frameDelay) != 1) {
455 return NS_ERROR_INVALID_ARG;
456 }
457
458 // xoffset=#
459 } else if (nsCRT::strcmp(token, "xoffset") == 0 && offsetX) {
460 if (!value) {
461 return NS_ERROR_INVALID_ARG;
462 }
463
464 if (PR_sscanf(value, "%u", offsetX) != 1) {
465 return NS_ERROR_INVALID_ARG;
466 }
467
468 // yoffset=#
469 } else if (nsCRT::strcmp(token, "yoffset") == 0 && offsetY) {
470 if (!value) {
471 return NS_ERROR_INVALID_ARG;
472 }
473
474 if (PR_sscanf(value, "%u", offsetY) != 1) {
475 return NS_ERROR_INVALID_ARG;
476 }
477
478 // unknown token name
479 } else
480 return NS_ERROR_INVALID_ARG;
481
482 if (value) {
483 *equals = '='; // restore '=' so strtok doesn't get lost
484 }
485 }
486
487 #endif
488 return NS_OK;
489 }
490
491 NS_IMETHODIMP
Close()492 nsPNGEncoder::Close() {
493 if (mImageBuffer != nullptr) {
494 free(mImageBuffer);
495 mImageBuffer = nullptr;
496 mImageBufferSize = 0;
497 mImageBufferUsed = 0;
498 mImageBufferReadPoint = 0;
499 }
500 return NS_OK;
501 }
502
503 NS_IMETHODIMP
Available(uint64_t * _retval)504 nsPNGEncoder::Available(uint64_t* _retval) {
505 if (!mImageBuffer) {
506 return NS_BASE_STREAM_CLOSED;
507 }
508
509 *_retval = mImageBufferUsed - mImageBufferReadPoint;
510 return NS_OK;
511 }
512
513 NS_IMETHODIMP
Read(char * aBuf,uint32_t aCount,uint32_t * _retval)514 nsPNGEncoder::Read(char* aBuf, uint32_t aCount, uint32_t* _retval) {
515 return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, _retval);
516 }
517
518 NS_IMETHODIMP
ReadSegments(nsWriteSegmentFun aWriter,void * aClosure,uint32_t aCount,uint32_t * _retval)519 nsPNGEncoder::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
520 uint32_t aCount, uint32_t* _retval) {
521 // Avoid another thread reallocing the buffer underneath us
522 ReentrantMonitorAutoEnter autoEnter(mReentrantMonitor);
523
524 uint32_t maxCount = mImageBufferUsed - mImageBufferReadPoint;
525 if (maxCount == 0) {
526 *_retval = 0;
527 return mFinished ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK;
528 }
529
530 if (aCount > maxCount) {
531 aCount = maxCount;
532 }
533
534 nsresult rv = aWriter(
535 this, aClosure,
536 reinterpret_cast<const char*>(mImageBuffer + mImageBufferReadPoint), 0,
537 aCount, _retval);
538 if (NS_SUCCEEDED(rv)) {
539 NS_ASSERTION(*_retval <= aCount, "bad write count");
540 mImageBufferReadPoint += *_retval;
541 }
542
543 // errors returned from the writer end here!
544 return NS_OK;
545 }
546
547 NS_IMETHODIMP
IsNonBlocking(bool * _retval)548 nsPNGEncoder::IsNonBlocking(bool* _retval) {
549 *_retval = true;
550 return NS_OK;
551 }
552
553 NS_IMETHODIMP
AsyncWait(nsIInputStreamCallback * aCallback,uint32_t aFlags,uint32_t aRequestedCount,nsIEventTarget * aTarget)554 nsPNGEncoder::AsyncWait(nsIInputStreamCallback* aCallback, uint32_t aFlags,
555 uint32_t aRequestedCount, nsIEventTarget* aTarget) {
556 if (aFlags != 0) {
557 return NS_ERROR_NOT_IMPLEMENTED;
558 }
559
560 if (mCallback || mCallbackTarget) {
561 return NS_ERROR_UNEXPECTED;
562 }
563
564 mCallbackTarget = aTarget;
565 // 0 means "any number of bytes except 0"
566 mNotifyThreshold = aRequestedCount;
567 if (!aRequestedCount) {
568 mNotifyThreshold = 1024; // We don't want to notify incessantly
569 }
570
571 // We set the callback absolutely last, because NotifyListener uses it to
572 // determine if someone needs to be notified. If we don't set it last,
573 // NotifyListener might try to fire off a notification to a null target
574 // which will generally cause non-threadsafe objects to be used off the main
575 // thread
576 mCallback = aCallback;
577
578 // What we are being asked for may be present already
579 NotifyListener();
580 return NS_OK;
581 }
582
583 NS_IMETHODIMP
CloseWithStatus(nsresult aStatus)584 nsPNGEncoder::CloseWithStatus(nsresult aStatus) { return Close(); }
585
586 // nsPNGEncoder::ConvertHostARGBRow
587 //
588 // Our colors are stored with premultiplied alphas, but PNGs use
589 // post-multiplied alpha. This swaps to PNG-style alpha.
590 //
591 // Copied from gfx/cairo/cairo/src/cairo-png.c
592
ConvertHostARGBRow(const uint8_t * aSrc,uint8_t * aDest,uint32_t aPixelWidth,bool aUseTransparency)593 void nsPNGEncoder::ConvertHostARGBRow(const uint8_t* aSrc, uint8_t* aDest,
594 uint32_t aPixelWidth,
595 bool aUseTransparency) {
596 uint32_t pixelStride = aUseTransparency ? 4 : 3;
597 for (uint32_t x = 0; x < aPixelWidth; x++) {
598 const uint32_t& pixelIn = ((const uint32_t*)(aSrc))[x];
599 uint8_t* pixelOut = &aDest[x * pixelStride];
600
601 uint8_t alpha = (pixelIn & 0xff000000) >> 24;
602 pixelOut[pixelStride - 1] = alpha; // overwritten below if pixelStride == 3
603 if (alpha == 255) {
604 pixelOut[0] = (pixelIn & 0xff0000) >> 16;
605 pixelOut[1] = (pixelIn & 0x00ff00) >> 8;
606 pixelOut[2] = (pixelIn & 0x0000ff);
607 } else if (alpha == 0) {
608 pixelOut[0] = pixelOut[1] = pixelOut[2] = 0;
609 } else {
610 pixelOut[0] = (((pixelIn & 0xff0000) >> 16) * 255 + alpha / 2) / alpha;
611 pixelOut[1] = (((pixelIn & 0x00ff00) >> 8) * 255 + alpha / 2) / alpha;
612 pixelOut[2] = (((pixelIn & 0x0000ff)) * 255 + alpha / 2) / alpha;
613 }
614 }
615 }
616
617 // nsPNGEncoder::StripAlpha
618 //
619 // Input is RGBA, output is RGB
620
StripAlpha(const uint8_t * aSrc,uint8_t * aDest,uint32_t aPixelWidth)621 void nsPNGEncoder::StripAlpha(const uint8_t* aSrc, uint8_t* aDest,
622 uint32_t aPixelWidth) {
623 for (uint32_t x = 0; x < aPixelWidth; x++) {
624 const uint8_t* pixelIn = &aSrc[x * 4];
625 uint8_t* pixelOut = &aDest[x * 3];
626 pixelOut[0] = pixelIn[0];
627 pixelOut[1] = pixelIn[1];
628 pixelOut[2] = pixelIn[2];
629 }
630 }
631
632 // nsPNGEncoder::WarningCallback
633
WarningCallback(png_structp png_ptr,png_const_charp warning_msg)634 void nsPNGEncoder::WarningCallback(png_structp png_ptr,
635 png_const_charp warning_msg) {
636 MOZ_LOG(sPNGEncoderLog, LogLevel::Warning,
637 ("libpng warning: %s\n", warning_msg));
638 }
639
640 // nsPNGEncoder::ErrorCallback
641
ErrorCallback(png_structp png_ptr,png_const_charp error_msg)642 void nsPNGEncoder::ErrorCallback(png_structp png_ptr,
643 png_const_charp error_msg) {
644 MOZ_LOG(sPNGEncoderLog, LogLevel::Error, ("libpng error: %s\n", error_msg));
645 png_longjmp(png_ptr, 1);
646 }
647
648 // nsPNGEncoder::WriteCallback
649
650 void // static
WriteCallback(png_structp png,png_bytep data,png_size_t size)651 nsPNGEncoder::WriteCallback(png_structp png, png_bytep data, png_size_t size) {
652 nsPNGEncoder* that = static_cast<nsPNGEncoder*>(png_get_io_ptr(png));
653 if (!that->mImageBuffer) {
654 return;
655 }
656
657 CheckedUint32 sizeNeeded = CheckedUint32(that->mImageBufferUsed) + size;
658 if (!sizeNeeded.isValid()) {
659 // Take the lock to ensure that nobody is trying to read from the buffer
660 // we are destroying
661 ReentrantMonitorAutoEnter autoEnter(that->mReentrantMonitor);
662
663 that->NullOutImageBuffer();
664 return;
665 }
666
667 if (sizeNeeded.value() > that->mImageBufferSize) {
668 // When we're reallocing the buffer we need to take the lock to ensure
669 // that nobody is trying to read from the buffer we are destroying
670 ReentrantMonitorAutoEnter autoEnter(that->mReentrantMonitor);
671
672 while (sizeNeeded.value() > that->mImageBufferSize) {
673 // expand buffer, just double each time
674 CheckedUint32 bufferSize = CheckedUint32(that->mImageBufferSize) * 2;
675 if (!bufferSize.isValid()) {
676 that->NullOutImageBuffer();
677 return;
678 }
679 that->mImageBufferSize *= 2;
680 uint8_t* newBuf =
681 (uint8_t*)realloc(that->mImageBuffer, that->mImageBufferSize);
682 if (!newBuf) {
683 // can't resize, just zero (this will keep us from writing more)
684 that->NullOutImageBuffer();
685 return;
686 }
687 that->mImageBuffer = newBuf;
688 }
689 }
690
691 memcpy(&that->mImageBuffer[that->mImageBufferUsed], data, size);
692 that->mImageBufferUsed += size;
693 that->NotifyListener();
694 }
695
NullOutImageBuffer()696 void nsPNGEncoder::NullOutImageBuffer() {
697 mReentrantMonitor.AssertCurrentThreadIn();
698
699 free(mImageBuffer);
700 mImageBuffer = nullptr;
701 mImageBufferSize = 0;
702 mImageBufferUsed = 0;
703 }
704
NotifyListener()705 void nsPNGEncoder::NotifyListener() {
706 // We might call this function on multiple threads (any threads that call
707 // AsyncWait and any that do encoding) so we lock to avoid notifying the
708 // listener twice about the same data (which generally leads to a truncated
709 // image).
710 ReentrantMonitorAutoEnter autoEnter(mReentrantMonitor);
711
712 if (mCallback &&
713 (mImageBufferUsed - mImageBufferReadPoint >= mNotifyThreshold ||
714 mFinished)) {
715 nsCOMPtr<nsIInputStreamCallback> callback;
716 if (mCallbackTarget) {
717 callback = NS_NewInputStreamReadyEvent("nsPNGEncoder::NotifyListener",
718 mCallback, mCallbackTarget);
719 } else {
720 callback = mCallback;
721 }
722
723 NS_ASSERTION(callback, "Shouldn't fail to make the callback");
724 // Null the callback first because OnInputStreamReady could reenter
725 // AsyncWait
726 mCallback = nullptr;
727 mCallbackTarget = nullptr;
728 mNotifyThreshold = 0;
729
730 callback->OnInputStreamReady(this);
731 }
732 }
733