PageRenderTime 68ms CodeModel.GetById 36ms RepoModel.GetById 0ms app.codeStats 0ms

/image/encoders/png/nsPNGEncoder.cpp

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