PageRenderTime 89ms CodeModel.GetById 44ms app.highlight 38ms RepoModel.GetById 1ms app.codeStats 0ms

/image/encoders/png/nsPNGEncoder.cpp

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