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