Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/image/encoders/png/nsPNGEncoder.cpp
Line
Count
Source (jump to first uncovered line)
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
NS_IMPL_ISUPPORTS(nsPNGEncoder, imgIEncoder, nsIInputStream,
18
                  nsIAsyncInputStream)
19
20
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
                                              "nsPNGEncoder.mReentrantMonitor")
29
0
{ }
30
31
nsPNGEncoder::~nsPNGEncoder()
32
0
{
33
0
  if (mImageBuffer) {
34
0
    free(mImageBuffer);
35
0
    mImageBuffer = nullptr;
36
0
  }
37
0
  // don't leak if EndImageEncode wasn't called
38
0
  if (mPNG) {
39
0
    png_destroy_write_struct(&mPNG, &mPNGinfo);
40
0
  }
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
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
0
{
60
0
  NS_ENSURE_ARG(aData);
61
0
  nsresult rv;
62
0
63
0
  rv = StartImageEncode(aWidth, aHeight, aInputFormat, aOutputOptions);
64
0
  if (!NS_SUCCEEDED(rv)) {
65
0
    return rv;
66
0
  }
67
0
68
0
  rv = AddImageFrame(aData, aLength, aWidth, aHeight, aStride,
69
0
                     aInputFormat, aOutputOptions);
70
0
  if (!NS_SUCCEEDED(rv)) {
71
0
    return rv;
72
0
  }
73
0
74
0
  rv = EndImageEncode();
75
0
76
0
  return rv;
77
0
}
78
79
80
// nsPNGEncoder::StartImageEncode
81
//
82
//
83
// See ::InitFromData for other info.
84
NS_IMETHODIMP
85
nsPNGEncoder::StartImageEncode(uint32_t aWidth,
86
                               uint32_t aHeight,
87
                               uint32_t aInputFormat,
88
                               const nsAString& aOutputOptions)
89
0
{
90
0
  bool useTransparency = true, skipFirstFrame = false;
91
0
  uint32_t numFrames = 1;
92
0
  uint32_t numPlays = 0; // For animations, 0 == forever
93
0
94
0
  // can't initialize more than once
95
0
  if (mImageBuffer != nullptr) {
96
0
    return NS_ERROR_ALREADY_INITIALIZED;
97
0
  }
98
0
99
0
  // validate input format
100
0
  if (aInputFormat != INPUT_FORMAT_RGB &&
101
0
      aInputFormat != INPUT_FORMAT_RGBA &&
102
0
      aInputFormat != INPUT_FORMAT_HOSTARGB)
103
0
    return NS_ERROR_INVALID_ARG;
104
0
105
0
  // parse and check any provided output options
106
0
  nsresult rv = ParseOptions(aOutputOptions, &useTransparency, &skipFirstFrame,
107
0
                             &numFrames, &numPlays, nullptr, nullptr,
108
0
                             nullptr, nullptr, nullptr);
109
0
  if (rv != NS_OK) {
110
0
    return rv;
111
0
  }
112
0
113
0
#ifdef PNG_APNG_SUPPORTED
114
0
  if (numFrames > 1) {
115
0
    mIsAnimation = true;
116
0
  }
117
0
118
0
#endif
119
0
120
0
  // initialize
121
0
  mPNG = png_create_write_struct(PNG_LIBPNG_VER_STRING,
122
0
                                 nullptr,
123
0
                                 ErrorCallback,
124
0
                                 WarningCallback);
125
0
  if (!mPNG) {
126
0
    return NS_ERROR_OUT_OF_MEMORY;
127
0
  }
128
0
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
0
  }
134
0
135
0
  // libpng's error handler jumps back here upon an error.
136
0
  // Note: It's important that all png_* callers do this, or errors
137
0
  // 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
0
  }
142
0
143
0
  // Set up to read the data into our image buffer, start out with an 8K
144
0
  // estimated size. Note: we don't have to worry about freeing this data
145
0
  // 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
0
  }
152
0
  mImageBufferUsed = 0;
153
0
154
0
  // set our callback for libpng to give us the data
155
0
  png_set_write_fn(mPNG, this, WriteCallback, nullptr);
156
0
157
0
  // include alpha?
158
0
  int colorType;
159
0
  if ((aInputFormat == INPUT_FORMAT_HOSTARGB ||
160
0
       aInputFormat == INPUT_FORMAT_RGBA)  &&
161
0
       useTransparency)
162
0
    colorType = PNG_COLOR_TYPE_RGB_ALPHA;
163
0
  else
164
0
    colorType = PNG_COLOR_TYPE_RGB;
165
0
166
0
  png_set_IHDR(mPNG, mPNGinfo, aWidth, aHeight, 8, colorType,
167
0
               PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
168
0
               PNG_FILTER_TYPE_DEFAULT);
169
0
170
0
#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
0
  }
175
0
#endif
176
0
177
0
  // XXX: support PLTE, gAMA, tRNS, bKGD?
178
0
179
0
  png_write_info(mPNG, mPNGinfo);
180
0
181
0
  return NS_OK;
182
0
}
183
184
// Returns the number of bytes in the image buffer used.
185
NS_IMETHODIMP
186
nsPNGEncoder::GetImageBufferUsed(uint32_t* aOutputSize)
187
0
{
188
0
  NS_ENSURE_ARG_POINTER(aOutputSize);
189
0
  *aOutputSize = mImageBufferUsed;
190
0
  return NS_OK;
191
0
}
192
193
// Returns a pointer to the start of the image buffer
194
NS_IMETHODIMP
195
nsPNGEncoder::GetImageBuffer(char** aOutputBuffer)
196
0
{
197
0
  NS_ENSURE_ARG_POINTER(aOutputBuffer);
198
0
  *aOutputBuffer = reinterpret_cast<char*>(mImageBuffer);
199
0
  return NS_OK;
200
0
}
201
202
NS_IMETHODIMP
203
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
0
{
211
0
  bool useTransparency= true;
212
0
  uint32_t delay_ms = 500;
213
0
#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
  uint32_t x_offset = 0, y_offset = 0;
221
0
222
0
  // must be initialized
223
0
  if (mImageBuffer == nullptr) {
224
0
    return NS_ERROR_NOT_INITIALIZED;
225
0
  }
226
0
227
0
  // EndImageEncode was done, or some error occurred earlier
228
0
  if (!mPNG) {
229
0
    return NS_BASE_STREAM_CLOSED;
230
0
  }
231
0
232
0
  // validate input format
233
0
  if (aInputFormat != INPUT_FORMAT_RGB &&
234
0
      aInputFormat != INPUT_FORMAT_RGBA &&
235
0
      aInputFormat != INPUT_FORMAT_HOSTARGB)
236
0
    return NS_ERROR_INVALID_ARG;
237
0
238
0
  // 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
0
  }
243
0
244
0
  // parse and check any provided output options
245
0
  nsresult rv = ParseOptions(aFrameOptions, &useTransparency, nullptr,
246
0
                             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
0
  }
251
0
252
0
#ifdef PNG_APNG_SUPPORTED
253
0
  if (mIsAnimation) {
254
0
    // XXX the row pointers arg (#3) is unused, can it be removed?
255
0
    png_write_frame_head(mPNG, mPNGinfo, nullptr,
256
0
                         aWidth, aHeight, x_offset, y_offset,
257
0
                         delay_ms, 1000, dispose_op, blend_op);
258
0
  }
259
0
#endif
260
0
261
0
  // Stride is the padded width of each row, so it better be longer
262
0
  // (I'm afraid people will not understand what stride means, so
263
0
  // 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
0
  }
272
0
273
#ifdef PNG_WRITE_FILTER_SUPPORTED
274
  png_set_filter(mPNG, PNG_FILTER_TYPE_BASE, PNG_FILTER_VALUE_NONE);
275
#endif
276
277
0
  // write each row: if we add more input formats, we may want to
278
0
  // generalize the conversions
279
0
  if (aInputFormat == INPUT_FORMAT_HOSTARGB) {
280
0
    // PNG requires RGBA with post-multiplied alpha, so we need to
281
0
    // 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
0
    }
287
0
  } else if (aInputFormat == INPUT_FORMAT_RGBA && !useTransparency) {
288
0
    // 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
0
             aInputFormat == INPUT_FORMAT_RGBA) {
296
0
    // 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
0
301
0
  } else {
302
0
    MOZ_ASSERT_UNREACHABLE("Bad format type");
303
0
    return NS_ERROR_INVALID_ARG;
304
0
  }
305
0
306
0
#ifdef PNG_APNG_SUPPORTED
307
0
  if (mIsAnimation) {
308
0
    png_write_frame_tail(mPNG, mPNGinfo);
309
0
  }
310
0
#endif
311
0
312
0
  return NS_OK;
313
0
}
314
315
316
NS_IMETHODIMP
317
nsPNGEncoder::EndImageEncode()
318
0
{
319
0
  // must be initialized
320
0
  if (mImageBuffer == nullptr) {
321
0
    return NS_ERROR_NOT_INITIALIZED;
322
0
  }
323
0
324
0
  // EndImageEncode has already been called, or some error
325
0
  // occurred earlier
326
0
  if (!mPNG) {
327
0
    return NS_BASE_STREAM_CLOSED;
328
0
  }
329
0
330
0
  // 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
0
  }
335
0
336
0
  png_write_end(mPNG, mPNGinfo);
337
0
  png_destroy_write_struct(&mPNG, &mPNGinfo);
338
0
339
0
  mFinished = true;
340
0
  NotifyListener();
341
0
342
0
  // 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
0
  }
346
0
347
0
  return NS_OK;
348
0
}
349
350
351
nsresult
352
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
0
{
363
0
#ifdef PNG_APNG_SUPPORTED
364
0
  // 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
0
369
0
  while (char* token = nsCRT::strtok(options, ";", &options)) {
370
0
    // 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
0
    }
376
0
    if (*equals == '=') {
377
0
      value = equals + 1;
378
0
    }
379
0
380
0
    if (value) {
381
0
      *equals = '\0'; // temporary null
382
0
    }
383
0
384
0
    // 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
0
      }
389
0
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
0
      } else {
396
0
        return NS_ERROR_INVALID_ARG;
397
0
      }
398
0
399
0
    // skipfirstframe=[yes|no]
400
0
    } else if (nsCRT::strcmp(token, "skipfirstframe") == 0 &&
401
0
               skipFirstFrame) {
402
0
      if (!value) {
403
0
        return NS_ERROR_INVALID_ARG;
404
0
      }
405
0
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
0
      } else {
411
0
        return NS_ERROR_INVALID_ARG;
412
0
      }
413
0
414
0
    // frames=#
415
0
    } else if (nsCRT::strcmp(token, "frames") == 0 && numFrames) {
416
0
      if (!value) {
417
0
        return NS_ERROR_INVALID_ARG;
418
0
      }
419
0
420
0
      if (PR_sscanf(value, "%u", numFrames) != 1) {
421
0
        return NS_ERROR_INVALID_ARG;
422
0
      }
423
0
424
0
      // frames=0 is nonsense.
425
0
      if (*numFrames == 0) {
426
0
        return NS_ERROR_INVALID_ARG;
427
0
      }
428
0
429
0
    // plays=#
430
0
    } else if (nsCRT::strcmp(token, "plays") == 0 && numPlays) {
431
0
      if (!value) {
432
0
        return NS_ERROR_INVALID_ARG;
433
0
      }
434
0
435
0
      // plays=0 to loop forever, otherwise play sequence specified
436
0
      // number of times
437
0
      if (PR_sscanf(value, "%u", numPlays) != 1) {
438
0
        return NS_ERROR_INVALID_ARG;
439
0
      }
440
0
441
0
    // 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
0
      }
446
0
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
0
      } else {
454
0
        return NS_ERROR_INVALID_ARG;
455
0
      }
456
0
457
0
    // 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
0
      }
462
0
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
0
      } else {
468
0
        return NS_ERROR_INVALID_ARG;
469
0
      }
470
0
471
0
    // 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
0
      }
476
0
477
0
      if (PR_sscanf(value, "%u", frameDelay) != 1) {
478
0
        return NS_ERROR_INVALID_ARG;
479
0
      }
480
0
481
0
    // xoffset=#
482
0
    } else if (nsCRT::strcmp(token, "xoffset") == 0 && offsetX) {
483
0
      if (!value) {
484
0
        return NS_ERROR_INVALID_ARG;
485
0
      }
486
0
487
0
      if (PR_sscanf(value, "%u", offsetX) != 1) {
488
0
        return NS_ERROR_INVALID_ARG;
489
0
      }
490
0
491
0
    // yoffset=#
492
0
    } else if (nsCRT::strcmp(token, "yoffset") == 0 && offsetY) {
493
0
      if (!value) {
494
0
        return NS_ERROR_INVALID_ARG;
495
0
      }
496
0
497
0
      if (PR_sscanf(value, "%u", offsetY) != 1) {
498
0
        return NS_ERROR_INVALID_ARG;
499
0
      }
500
0
501
0
    // unknown token name
502
0
    } else
503
0
      return NS_ERROR_INVALID_ARG;
504
0
505
0
    if (value) {
506
0
      *equals = '='; // restore '=' so strtok doesn't get lost
507
0
    }
508
0
  }
509
0
510
0
#endif
511
0
  return NS_OK;
512
0
}
513
514
515
NS_IMETHODIMP
516
nsPNGEncoder::Close()
517
0
{
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
0
  }
525
0
  return NS_OK;
526
0
}
527
528
NS_IMETHODIMP
529
nsPNGEncoder::Available(uint64_t* _retval)
530
0
{
531
0
  if (!mImageBuffer) {
532
0
    return NS_BASE_STREAM_CLOSED;
533
0
    }
534
0
535
0
  *_retval = mImageBufferUsed - mImageBufferReadPoint;
536
0
  return NS_OK;
537
0
}
538
539
NS_IMETHODIMP
540
nsPNGEncoder::Read(char* aBuf, uint32_t aCount, uint32_t* _retval)
541
0
{
542
0
  return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, _retval);
543
0
}
544
545
NS_IMETHODIMP
546
nsPNGEncoder::ReadSegments(nsWriteSegmentFun aWriter,
547
                           void* aClosure, uint32_t aCount,
548
                           uint32_t* _retval)
549
0
{
550
0
  // Avoid another thread reallocing the buffer underneath us
551
0
  ReentrantMonitorAutoEnter autoEnter(mReentrantMonitor);
552
0
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
0
  }
558
0
559
0
  if (aCount > maxCount) {
560
0
    aCount = maxCount;
561
0
  }
562
0
563
0
  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
0
  }
571
0
572
0
  // errors returned from the writer end here!
573
0
  return NS_OK;
574
0
}
575
576
NS_IMETHODIMP
577
nsPNGEncoder::IsNonBlocking(bool* _retval)
578
0
{
579
0
  *_retval = true;
580
0
  return NS_OK;
581
0
}
582
583
NS_IMETHODIMP
584
nsPNGEncoder::AsyncWait(nsIInputStreamCallback* aCallback,
585
                        uint32_t aFlags,
586
                        uint32_t aRequestedCount,
587
                        nsIEventTarget* aTarget)
588
0
{
589
0
  if (aFlags != 0) {
590
0
    return NS_ERROR_NOT_IMPLEMENTED;
591
0
  }
592
0
593
0
  if (mCallback || mCallbackTarget) {
594
0
    return NS_ERROR_UNEXPECTED;
595
0
  }
596
0
597
0
  mCallbackTarget = aTarget;
598
0
  // 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
0
  }
603
0
604
0
  // We set the callback absolutely last, because NotifyListener uses it to
605
0
  // determine if someone needs to be notified.  If we don't set it last,
606
0
  // NotifyListener might try to fire off a notification to a null target
607
0
  // which will generally cause non-threadsafe objects to be used off the main
608
0
  // thread
609
0
  mCallback = aCallback;
610
0
611
0
  // What we are being asked for may be present already
612
0
  NotifyListener();
613
0
  return NS_OK;
614
0
}
615
616
NS_IMETHODIMP
617
nsPNGEncoder::CloseWithStatus(nsresult aStatus)
618
0
{
619
0
  return Close();
620
0
}
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
nsPNGEncoder::ConvertHostARGBRow(const uint8_t* aSrc, uint8_t* aDest,
631
                                 uint32_t aPixelWidth,
632
                                 bool aUseTransparency)
633
0
{
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
0
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
0
    } 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
0
    }
652
0
  }
653
0
}
654
655
656
// nsPNGEncoder::StripAlpha
657
//
658
//    Input is RGBA, output is RGB
659
660
void
661
nsPNGEncoder::StripAlpha(const uint8_t* aSrc, uint8_t* aDest,
662
                          uint32_t aPixelWidth)
663
0
{
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
0
  }
671
0
}
672
673
674
// nsPNGEncoder::WarningCallback
675
676
void
677
nsPNGEncoder::WarningCallback(png_structp png_ptr,
678
                            png_const_charp warning_msg)
679
0
{
680
0
  MOZ_LOG(sPNGEncoderLog, LogLevel::Warning,
681
0
         ("libpng warning: %s\n", warning_msg));
682
0
}
683
684
685
// nsPNGEncoder::ErrorCallback
686
687
void
688
nsPNGEncoder::ErrorCallback(png_structp png_ptr,
689
                            png_const_charp error_msg)
690
0
{
691
0
  MOZ_LOG(sPNGEncoderLog, LogLevel::Error, ("libpng error: %s\n", error_msg));
692
0
  png_longjmp(png_ptr, 1);
693
0
}
694
695
// nsPNGEncoder::WriteCallback
696
697
void // static
698
nsPNGEncoder::WriteCallback(png_structp png, png_bytep data,
699
                            png_size_t size)
700
0
{
701
0
  nsPNGEncoder* that = static_cast<nsPNGEncoder*>(png_get_io_ptr(png));
702
0
  if (!that->mImageBuffer) {
703
0
    return;
704
0
  }
705
0
706
0
  if (that->mImageBufferUsed + size > that->mImageBufferSize) {
707
0
    // When we're reallocing the buffer we need to take the lock to ensure
708
0
    // that nobody is trying to read from the buffer we are destroying
709
0
    ReentrantMonitorAutoEnter autoEnter(that->mReentrantMonitor);
710
0
711
0
    // 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
0
      // 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
0
    }
723
0
    that->mImageBuffer = newBuf;
724
0
  }
725
0
  memcpy(&that->mImageBuffer[that->mImageBufferUsed], data, size);
726
0
  that->mImageBufferUsed += size;
727
0
  that->NotifyListener();
728
0
}
729
730
void
731
nsPNGEncoder::NotifyListener()
732
0
{
733
0
  // We might call this function on multiple threads (any threads that call
734
0
  // AsyncWait and any that do encoding) so we lock to avoid notifying the
735
0
  // listener twice about the same data (which generally leads to a truncated
736
0
  // image).
737
0
  ReentrantMonitorAutoEnter autoEnter(mReentrantMonitor);
738
0
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
0
    } else {
747
0
      callback = mCallback;
748
0
    }
749
0
750
0
    NS_ASSERTION(callback, "Shouldn't fail to make the callback");
751
0
    // Null the callback first because OnInputStreamReady could reenter
752
0
    // AsyncWait
753
0
    mCallback = nullptr;
754
0
    mCallbackTarget = nullptr;
755
0
    mNotifyThreshold = 0;
756
0
757
0
    callback->OnInputStreamReady(this);
758
0
  }
759
0
}