LCOV - code coverage report
Current view: top level - image/encoders/png - nsPNGEncoder.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 0 328 0.0 %
Date: 2017-07-14 16:53:18 Functions: 0 25 0.0 %
Legend: Lines: hit not hit

          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 : }

Generated by: LCOV version 1.13