Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/image/encoders/jpeg/nsJPEGEncoder.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 "nsJPEGEncoder.h"
7
#include "prprf.h"
8
#include "nsString.h"
9
#include "nsStreamUtils.h"
10
#include "gfxColor.h"
11
12
#include <setjmp.h>
13
#include "jerror.h"
14
15
using namespace mozilla;
16
17
NS_IMPL_ISUPPORTS(nsJPEGEncoder, imgIEncoder, nsIInputStream,
18
                  nsIAsyncInputStream)
19
20
// used to pass error info through the JPEG library
21
struct encoder_error_mgr {
22
  jpeg_error_mgr pub;
23
  jmp_buf setjmp_buffer;
24
};
25
26
nsJPEGEncoder::nsJPEGEncoder()
27
   : mFinished(false),
28
     mImageBuffer(nullptr),
29
     mImageBufferSize(0),
30
     mImageBufferUsed(0),
31
     mImageBufferReadPoint(0),
32
     mCallback(nullptr),
33
     mCallbackTarget(nullptr),
34
     mNotifyThreshold(0),
35
     mReentrantMonitor("nsJPEGEncoder.mReentrantMonitor")
36
0
{
37
0
}
38
39
nsJPEGEncoder::~nsJPEGEncoder()
40
0
{
41
0
  if (mImageBuffer) {
42
0
    free(mImageBuffer);
43
0
    mImageBuffer = nullptr;
44
0
  }
45
0
}
46
47
48
// nsJPEGEncoder::InitFromData
49
//
50
//    One output option is supported: "quality=X" where X is an integer in the
51
//    range 0-100. Higher values for X give better quality.
52
//
53
//    Transparency is always discarded.
54
55
NS_IMETHODIMP
56
nsJPEGEncoder::InitFromData(const uint8_t* aData,
57
                            uint32_t aLength, // (unused, req'd by JS)
58
                            uint32_t aWidth,
59
                            uint32_t aHeight,
60
                            uint32_t aStride,
61
                            uint32_t aInputFormat,
62
                            const nsAString& aOutputOptions)
63
0
{
64
0
  NS_ENSURE_ARG(aData);
65
0
66
0
  // validate input format
67
0
  if (aInputFormat != INPUT_FORMAT_RGB &&
68
0
      aInputFormat != INPUT_FORMAT_RGBA &&
69
0
      aInputFormat != INPUT_FORMAT_HOSTARGB)
70
0
    return NS_ERROR_INVALID_ARG;
71
0
72
0
  // Stride is the padded width of each row, so it better be longer (I'm afraid
73
0
  // people will not understand what stride means, so check it well)
74
0
  if ((aInputFormat == INPUT_FORMAT_RGB &&
75
0
       aStride < aWidth * 3) ||
76
0
      ((aInputFormat == INPUT_FORMAT_RGBA ||
77
0
        aInputFormat == INPUT_FORMAT_HOSTARGB) &&
78
0
       aStride < aWidth * 4)) {
79
0
    NS_WARNING("Invalid stride for InitFromData");
80
0
    return NS_ERROR_INVALID_ARG;
81
0
  }
82
0
83
0
  // can't initialize more than once
84
0
  if (mImageBuffer != nullptr) {
85
0
    return NS_ERROR_ALREADY_INITIALIZED;
86
0
  }
87
0
88
0
  // options: we only have one option so this is easy
89
0
  int quality = 92;
90
0
  if (aOutputOptions.Length() > 0) {
91
0
    // have options string
92
0
    const nsString qualityPrefix(NS_LITERAL_STRING("quality="));
93
0
    if (aOutputOptions.Length() > qualityPrefix.Length()  &&
94
0
        StringBeginsWith(aOutputOptions, qualityPrefix)) {
95
0
      // have quality string
96
0
      nsCString value =
97
0
        NS_ConvertUTF16toUTF8(Substring(aOutputOptions,
98
0
                                        qualityPrefix.Length()));
99
0
      int newquality = -1;
100
0
      if (PR_sscanf(value.get(), "%d", &newquality) == 1) {
101
0
        if (newquality >= 0 && newquality <= 100) {
102
0
          quality = newquality;
103
0
        } else {
104
0
          NS_WARNING("Quality value out of range, should be 0-100,"
105
0
                     " using default");
106
0
        }
107
0
      } else {
108
0
        NS_WARNING("Quality value invalid, should be integer 0-100,"
109
0
                   " using default");
110
0
      }
111
0
    }
112
0
    else {
113
0
      return NS_ERROR_INVALID_ARG;
114
0
    }
115
0
  }
116
0
117
0
  jpeg_compress_struct cinfo;
118
0
119
0
  // We set up the normal JPEG error routines, then override error_exit.
120
0
  // This must be done before the call to create_compress
121
0
  encoder_error_mgr errmgr;
122
0
  cinfo.err = jpeg_std_error(&errmgr.pub);
123
0
  errmgr.pub.error_exit = errorExit;
124
0
  // Establish the setjmp return context for my_error_exit to use.
125
0
  if (setjmp(errmgr.setjmp_buffer)) {
126
0
    // If we get here, the JPEG code has signaled an error.
127
0
    // We need to clean up the JPEG object, close the input file, and return.
128
0
    return NS_ERROR_FAILURE;
129
0
  }
130
0
131
0
  jpeg_create_compress(&cinfo);
132
0
  cinfo.image_width = aWidth;
133
0
  cinfo.image_height = aHeight;
134
0
  cinfo.input_components = 3;
135
0
  cinfo.in_color_space = JCS_RGB;
136
0
  cinfo.data_precision = 8;
137
0
138
0
  jpeg_set_defaults(&cinfo);
139
0
  jpeg_set_quality(&cinfo, quality, 1); // quality here is 0-100
140
0
  if (quality >= 90) {
141
0
    int i;
142
0
    for (i=0; i < MAX_COMPONENTS; i++) {
143
0
      cinfo.comp_info[i].h_samp_factor=1;
144
0
      cinfo.comp_info[i].v_samp_factor=1;
145
0
    }
146
0
  }
147
0
148
0
  // set up the destination manager
149
0
  jpeg_destination_mgr destmgr;
150
0
  destmgr.init_destination = initDestination;
151
0
  destmgr.empty_output_buffer = emptyOutputBuffer;
152
0
  destmgr.term_destination = termDestination;
153
0
  cinfo.dest = &destmgr;
154
0
  cinfo.client_data = this;
155
0
156
0
  jpeg_start_compress(&cinfo, 1);
157
0
158
0
  // feed it the rows
159
0
  if (aInputFormat == INPUT_FORMAT_RGB) {
160
0
    while (cinfo.next_scanline < cinfo.image_height) {
161
0
      const uint8_t* row = &aData[cinfo.next_scanline * aStride];
162
0
      jpeg_write_scanlines(&cinfo, const_cast<uint8_t**>(&row), 1);
163
0
    }
164
0
  } else if (aInputFormat == INPUT_FORMAT_RGBA) {
165
0
    UniquePtr<uint8_t[]> rowptr = MakeUnique<uint8_t[]>(aWidth * 3);
166
0
    uint8_t* row = rowptr.get();
167
0
    while (cinfo.next_scanline < cinfo.image_height) {
168
0
      ConvertRGBARow(&aData[cinfo.next_scanline * aStride], row, aWidth);
169
0
      jpeg_write_scanlines(&cinfo, &row, 1);
170
0
    }
171
0
  } else if (aInputFormat == INPUT_FORMAT_HOSTARGB) {
172
0
    UniquePtr<uint8_t[]> rowptr = MakeUnique<uint8_t[]>(aWidth * 3);
173
0
    uint8_t* row = rowptr.get();
174
0
    while (cinfo.next_scanline < cinfo.image_height) {
175
0
      ConvertHostARGBRow(&aData[cinfo.next_scanline * aStride], row, aWidth);
176
0
      jpeg_write_scanlines(&cinfo, &row, 1);
177
0
    }
178
0
  }
179
0
180
0
  jpeg_finish_compress(&cinfo);
181
0
  jpeg_destroy_compress(&cinfo);
182
0
183
0
  mFinished = true;
184
0
  NotifyListener();
185
0
186
0
  // if output callback can't get enough memory, it will free our buffer
187
0
  if (!mImageBuffer) {
188
0
    return NS_ERROR_OUT_OF_MEMORY;
189
0
  }
190
0
191
0
  return NS_OK;
192
0
}
193
194
195
NS_IMETHODIMP
196
nsJPEGEncoder::StartImageEncode(uint32_t aWidth,
197
                                uint32_t aHeight,
198
                                uint32_t aInputFormat,
199
                                const nsAString& aOutputOptions)
200
0
{
201
0
  return NS_ERROR_NOT_IMPLEMENTED;
202
0
}
203
204
// Returns the number of bytes in the image buffer used.
205
NS_IMETHODIMP
206
nsJPEGEncoder::GetImageBufferUsed(uint32_t* aOutputSize)
207
0
{
208
0
  NS_ENSURE_ARG_POINTER(aOutputSize);
209
0
  *aOutputSize = mImageBufferUsed;
210
0
  return NS_OK;
211
0
}
212
213
// Returns a pointer to the start of the image buffer
214
NS_IMETHODIMP
215
nsJPEGEncoder::GetImageBuffer(char** aOutputBuffer)
216
0
{
217
0
  NS_ENSURE_ARG_POINTER(aOutputBuffer);
218
0
  *aOutputBuffer = reinterpret_cast<char*>(mImageBuffer);
219
0
  return NS_OK;
220
0
}
221
222
NS_IMETHODIMP
223
nsJPEGEncoder::AddImageFrame(const uint8_t* aData,
224
                             uint32_t aLength,
225
                             uint32_t aWidth,
226
                             uint32_t aHeight,
227
                             uint32_t aStride,
228
                             uint32_t aFrameFormat,
229
                             const nsAString& aFrameOptions)
230
0
{
231
0
  return NS_ERROR_NOT_IMPLEMENTED;
232
0
}
233
234
NS_IMETHODIMP
235
nsJPEGEncoder::EndImageEncode()
236
0
{
237
0
  return NS_ERROR_NOT_IMPLEMENTED;
238
0
}
239
240
241
NS_IMETHODIMP
242
nsJPEGEncoder::Close()
243
0
{
244
0
  if (mImageBuffer != nullptr) {
245
0
    free(mImageBuffer);
246
0
    mImageBuffer = nullptr;
247
0
    mImageBufferSize = 0;
248
0
    mImageBufferUsed = 0;
249
0
    mImageBufferReadPoint = 0;
250
0
  }
251
0
  return NS_OK;
252
0
}
253
254
NS_IMETHODIMP
255
nsJPEGEncoder::Available(uint64_t* _retval)
256
0
{
257
0
  if (!mImageBuffer) {
258
0
    return NS_BASE_STREAM_CLOSED;
259
0
  }
260
0
261
0
  *_retval = mImageBufferUsed - mImageBufferReadPoint;
262
0
  return NS_OK;
263
0
}
264
265
NS_IMETHODIMP
266
nsJPEGEncoder::Read(char* aBuf, uint32_t aCount, uint32_t* _retval)
267
0
{
268
0
  return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, _retval);
269
0
}
270
271
NS_IMETHODIMP
272
nsJPEGEncoder::ReadSegments(nsWriteSegmentFun aWriter,
273
                            void* aClosure, uint32_t aCount, uint32_t* _retval)
274
0
{
275
0
  // Avoid another thread reallocing the buffer underneath us
276
0
  ReentrantMonitorAutoEnter autoEnter(mReentrantMonitor);
277
0
278
0
  uint32_t maxCount = mImageBufferUsed - mImageBufferReadPoint;
279
0
  if (maxCount == 0) {
280
0
    *_retval = 0;
281
0
    return mFinished ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK;
282
0
  }
283
0
284
0
  if (aCount > maxCount) {
285
0
    aCount = maxCount;
286
0
  }
287
0
  nsresult rv = aWriter(this, aClosure,
288
0
                        reinterpret_cast<const char*>
289
0
                          (mImageBuffer+mImageBufferReadPoint),
290
0
                        0, aCount, _retval);
291
0
  if (NS_SUCCEEDED(rv)) {
292
0
    NS_ASSERTION(*_retval <= aCount, "bad write count");
293
0
    mImageBufferReadPoint += *_retval;
294
0
  }
295
0
296
0
  // errors returned from the writer end here!
297
0
  return NS_OK;
298
0
}
299
300
NS_IMETHODIMP
301
nsJPEGEncoder::IsNonBlocking(bool* _retval)
302
0
{
303
0
  *_retval = true;
304
0
  return NS_OK;
305
0
}
306
307
NS_IMETHODIMP
308
nsJPEGEncoder::AsyncWait(nsIInputStreamCallback* aCallback,
309
                         uint32_t aFlags, uint32_t aRequestedCount,
310
                         nsIEventTarget* aTarget)
311
0
{
312
0
  if (aFlags != 0) {
313
0
    return NS_ERROR_NOT_IMPLEMENTED;
314
0
  }
315
0
316
0
  if (mCallback || mCallbackTarget) {
317
0
    return NS_ERROR_UNEXPECTED;
318
0
  }
319
0
320
0
  mCallbackTarget = aTarget;
321
0
  // 0 means "any number of bytes except 0"
322
0
  mNotifyThreshold = aRequestedCount;
323
0
  if (!aRequestedCount) {
324
0
    mNotifyThreshold = 1024; // 1 KB seems good.  We don't want to
325
0
                             // notify incessantly
326
0
  }
327
0
328
0
  // We set the callback absolutely last, because NotifyListener uses it to
329
0
  // determine if someone needs to be notified.  If we don't set it last,
330
0
  // NotifyListener might try to fire off a notification to a null target
331
0
  // which will generally cause non-threadsafe objects to be used off the
332
0
  // main thread
333
0
  mCallback = aCallback;
334
0
335
0
  // What we are being asked for may be present already
336
0
  NotifyListener();
337
0
  return NS_OK;
338
0
}
339
340
NS_IMETHODIMP
341
nsJPEGEncoder::CloseWithStatus(nsresult aStatus)
342
0
{
343
0
  return Close();
344
0
}
345
346
347
348
// nsJPEGEncoder::ConvertHostARGBRow
349
//
350
//    Our colors are stored with premultiplied alphas, but we need
351
//    an output with no alpha in machine-independent byte order.
352
//
353
//    See gfx/cairo/cairo/src/cairo-png.c
354
void
355
nsJPEGEncoder::ConvertHostARGBRow(const uint8_t* aSrc, uint8_t* aDest,
356
                                  uint32_t aPixelWidth)
357
0
{
358
0
  for (uint32_t x = 0; x < aPixelWidth; x++) {
359
0
    const uint32_t& pixelIn = ((const uint32_t*)(aSrc))[x];
360
0
    uint8_t* pixelOut = &aDest[x * 3];
361
0
362
0
    pixelOut[0] = (pixelIn & 0xff0000) >> 16;
363
0
    pixelOut[1] = (pixelIn & 0x00ff00) >>  8;
364
0
    pixelOut[2] = (pixelIn & 0x0000ff) >>  0;
365
0
  }
366
0
}
367
368
/**
369
 * nsJPEGEncoder::ConvertRGBARow
370
 *
371
 * Input is RGBA, output is RGB, so we should alpha-premultiply.
372
 */
373
void
374
nsJPEGEncoder::ConvertRGBARow(const uint8_t* aSrc, uint8_t* aDest,
375
                              uint32_t aPixelWidth)
376
0
{
377
0
  for (uint32_t x = 0; x < aPixelWidth; x++) {
378
0
    const uint8_t* pixelIn = &aSrc[x * 4];
379
0
    uint8_t* pixelOut = &aDest[x * 3];
380
0
381
0
    uint8_t alpha = pixelIn[3];
382
0
    pixelOut[0] = gfxPreMultiply(pixelIn[0], alpha);
383
0
    pixelOut[1] = gfxPreMultiply(pixelIn[1], alpha);
384
0
    pixelOut[2] = gfxPreMultiply(pixelIn[2], alpha);
385
0
  }
386
0
}
387
388
// nsJPEGEncoder::initDestination
389
//
390
//    Initialize destination. This is called by jpeg_start_compress() before
391
//    any data is actually written. It must initialize next_output_byte and
392
//    free_in_buffer. free_in_buffer must be initialized to a positive value.
393
394
void // static
395
nsJPEGEncoder::initDestination(jpeg_compress_struct* cinfo)
396
0
{
397
0
  nsJPEGEncoder* that = static_cast<nsJPEGEncoder*>(cinfo->client_data);
398
0
  NS_ASSERTION(!that->mImageBuffer, "Image buffer already initialized");
399
0
400
0
  that->mImageBufferSize = 8192;
401
0
  that->mImageBuffer = (uint8_t*)malloc(that->mImageBufferSize);
402
0
  that->mImageBufferUsed = 0;
403
0
404
0
  cinfo->dest->next_output_byte = that->mImageBuffer;
405
0
  cinfo->dest->free_in_buffer = that->mImageBufferSize;
406
0
}
407
408
409
// nsJPEGEncoder::emptyOutputBuffer
410
//
411
//    This is called whenever the buffer has filled (free_in_buffer reaches
412
//    zero).  In typical applications, it should write out the *entire* buffer
413
//    (use the saved start address and buffer length; ignore the current state
414
//    of next_output_byte and free_in_buffer).  Then reset the pointer & count
415
//    to the start of the buffer, and return TRUE indicating that the buffer
416
//    has been dumped.  free_in_buffer must be set to a positive value when
417
//    TRUE is returned.  A FALSE return should only be used when I/O suspension
418
//    is desired (this operating mode is discussed in the next section).
419
420
boolean // static
421
nsJPEGEncoder::emptyOutputBuffer(jpeg_compress_struct* cinfo)
422
0
{
423
0
  nsJPEGEncoder* that = static_cast<nsJPEGEncoder*>(cinfo->client_data);
424
0
  NS_ASSERTION(that->mImageBuffer, "No buffer to empty!");
425
0
426
0
  // When we're reallocing the buffer we need to take the lock to ensure
427
0
  // that nobody is trying to read from the buffer we are destroying
428
0
  ReentrantMonitorAutoEnter autoEnter(that->mReentrantMonitor);
429
0
430
0
  that->mImageBufferUsed = that->mImageBufferSize;
431
0
432
0
  // expand buffer, just double size each time
433
0
  that->mImageBufferSize *= 2;
434
0
435
0
  uint8_t* newBuf = (uint8_t*)realloc(that->mImageBuffer,
436
0
                                      that->mImageBufferSize);
437
0
  if (!newBuf) {
438
0
    // can't resize, just zero (this will keep us from writing more)
439
0
    free(that->mImageBuffer);
440
0
    that->mImageBuffer = nullptr;
441
0
    that->mImageBufferSize = 0;
442
0
    that->mImageBufferUsed = 0;
443
0
444
0
    // This seems to be the only way to do errors through the JPEG library.  We
445
0
    // pass an nsresult masquerading as an int, which works because the
446
0
    // setjmp() caller casts it back.
447
0
    longjmp(((encoder_error_mgr*)(cinfo->err))->setjmp_buffer,
448
0
            static_cast<int>(NS_ERROR_OUT_OF_MEMORY));
449
0
  }
450
0
  that->mImageBuffer = newBuf;
451
0
452
0
  cinfo->dest->next_output_byte = &that->mImageBuffer[that->mImageBufferUsed];
453
0
  cinfo->dest->free_in_buffer = that->mImageBufferSize - that->mImageBufferUsed;
454
0
  return 1;
455
0
}
456
457
458
// nsJPEGEncoder::termDestination
459
//
460
//    Terminate destination --- called by jpeg_finish_compress() after all data
461
//    has been written.  In most applications, this must flush any data
462
//    remaining in the buffer.  Use either next_output_byte or free_in_buffer
463
//    to determine how much data is in the buffer.
464
465
void // static
466
nsJPEGEncoder::termDestination(jpeg_compress_struct* cinfo)
467
0
{
468
0
  nsJPEGEncoder* that = static_cast<nsJPEGEncoder*>(cinfo->client_data);
469
0
  if (!that->mImageBuffer) {
470
0
    return;
471
0
  }
472
0
  that->mImageBufferUsed = cinfo->dest->next_output_byte - that->mImageBuffer;
473
0
  NS_ASSERTION(that->mImageBufferUsed < that->mImageBufferSize,
474
0
               "JPEG library busted, got a bad image buffer size");
475
0
  that->NotifyListener();
476
0
}
477
478
479
// nsJPEGEncoder::errorExit
480
//
481
//    Override the standard error method in the IJG JPEG decoder code. This
482
//    was mostly copied from nsJPEGDecoder.cpp
483
484
void // static
485
nsJPEGEncoder::errorExit(jpeg_common_struct* cinfo)
486
0
{
487
0
  nsresult error_code;
488
0
  encoder_error_mgr* err = (encoder_error_mgr*) cinfo->err;
489
0
490
0
  // Convert error to a browser error code
491
0
  switch (cinfo->err->msg_code) {
492
0
    case JERR_OUT_OF_MEMORY:
493
0
      error_code = NS_ERROR_OUT_OF_MEMORY;
494
0
      break;
495
0
    default:
496
0
      error_code = NS_ERROR_FAILURE;
497
0
  }
498
0
499
0
  // Return control to the setjmp point.  We pass an nsresult masquerading as
500
0
  // an int, which works because the setjmp() caller casts it back.
501
0
  longjmp(err->setjmp_buffer, static_cast<int>(error_code));
502
0
}
503
504
void
505
nsJPEGEncoder::NotifyListener()
506
0
{
507
0
  // We might call this function on multiple threads (any threads that call
508
0
  // AsyncWait and any that do encoding) so we lock to avoid notifying the
509
0
  // listener twice about the same data (which generally leads to a truncated
510
0
  // image).
511
0
  ReentrantMonitorAutoEnter autoEnter(mReentrantMonitor);
512
0
513
0
  if (mCallback &&
514
0
      (mImageBufferUsed - mImageBufferReadPoint >= mNotifyThreshold ||
515
0
       mFinished)) {
516
0
    nsCOMPtr<nsIInputStreamCallback> callback;
517
0
    if (mCallbackTarget) {
518
0
      callback = NS_NewInputStreamReadyEvent("nsJPEGEncoder::NotifyListener",
519
0
                                             mCallback, mCallbackTarget);
520
0
    } else {
521
0
      callback = mCallback;
522
0
    }
523
0
524
0
    NS_ASSERTION(callback, "Shouldn't fail to make the callback");
525
0
    // Null the callback first because OnInputStreamReady could reenter
526
0
    // AsyncWait
527
0
    mCallback = nullptr;
528
0
    mCallbackTarget = nullptr;
529
0
    mNotifyThreshold = 0;
530
0
531
0
    callback->OnInputStreamReady(this);
532
0
  }
533
0
}