nsICOEncoder.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  1. /* This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this
  3. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. #include "nsCRT.h"
  5. #include "mozilla/EndianUtils.h"
  6. #include "nsBMPEncoder.h"
  7. #include "nsPNGEncoder.h"
  8. #include "nsICOEncoder.h"
  9. #include "prprf.h"
  10. #include "nsString.h"
  11. #include "nsStreamUtils.h"
  12. #include "nsTArray.h"
  13. using namespace mozilla;
  14. using namespace mozilla::image;
  15. NS_IMPL_ISUPPORTS(nsICOEncoder, imgIEncoder, nsIInputStream,
  16. nsIAsyncInputStream)
  17. nsICOEncoder::nsICOEncoder() : mImageBufferStart(nullptr),
  18. mImageBufferCurr(0),
  19. mImageBufferSize(0),
  20. mImageBufferReadPoint(0),
  21. mFinished(false),
  22. mUsePNG(true),
  23. mNotifyThreshold(0)
  24. {
  25. }
  26. nsICOEncoder::~nsICOEncoder()
  27. {
  28. if (mImageBufferStart) {
  29. free(mImageBufferStart);
  30. mImageBufferStart = nullptr;
  31. mImageBufferCurr = nullptr;
  32. }
  33. }
  34. // nsICOEncoder::InitFromData
  35. // Two output options are supported: format=<png|bmp>;bpp=<bpp_value>
  36. // format specifies whether to use png or bitmap format
  37. // bpp specifies the bits per pixel to use where bpp_value can be 24 or 32
  38. NS_IMETHODIMP
  39. nsICOEncoder::InitFromData(const uint8_t* aData,
  40. uint32_t aLength,
  41. uint32_t aWidth,
  42. uint32_t aHeight,
  43. uint32_t aStride,
  44. uint32_t aInputFormat,
  45. const nsAString& aOutputOptions)
  46. {
  47. // validate input format
  48. if (aInputFormat != INPUT_FORMAT_RGB &&
  49. aInputFormat != INPUT_FORMAT_RGBA &&
  50. aInputFormat != INPUT_FORMAT_HOSTARGB) {
  51. return NS_ERROR_INVALID_ARG;
  52. }
  53. // Stride is the padded width of each row, so it better be longer
  54. if ((aInputFormat == INPUT_FORMAT_RGB &&
  55. aStride < aWidth * 3) ||
  56. ((aInputFormat == INPUT_FORMAT_RGBA ||
  57. aInputFormat == INPUT_FORMAT_HOSTARGB) &&
  58. aStride < aWidth * 4)) {
  59. NS_WARNING("Invalid stride for InitFromData");
  60. return NS_ERROR_INVALID_ARG;
  61. }
  62. nsresult rv;
  63. rv = StartImageEncode(aWidth, aHeight, aInputFormat, aOutputOptions);
  64. NS_ENSURE_SUCCESS(rv, rv);
  65. rv = AddImageFrame(aData, aLength, aWidth, aHeight, aStride,
  66. aInputFormat, aOutputOptions);
  67. NS_ENSURE_SUCCESS(rv, rv);
  68. rv = EndImageEncode();
  69. return rv;
  70. }
  71. // Returns the number of bytes in the image buffer used
  72. // For an ICO file, this is all bytes in the buffer.
  73. NS_IMETHODIMP
  74. nsICOEncoder::GetImageBufferUsed(uint32_t* aOutputSize)
  75. {
  76. NS_ENSURE_ARG_POINTER(aOutputSize);
  77. *aOutputSize = mImageBufferSize;
  78. return NS_OK;
  79. }
  80. // Returns a pointer to the start of the image buffer
  81. NS_IMETHODIMP
  82. nsICOEncoder::GetImageBuffer(char** aOutputBuffer)
  83. {
  84. NS_ENSURE_ARG_POINTER(aOutputBuffer);
  85. *aOutputBuffer = reinterpret_cast<char*>(mImageBufferStart);
  86. return NS_OK;
  87. }
  88. NS_IMETHODIMP
  89. nsICOEncoder::AddImageFrame(const uint8_t* aData,
  90. uint32_t aLength,
  91. uint32_t aWidth,
  92. uint32_t aHeight,
  93. uint32_t aStride,
  94. uint32_t aInputFormat,
  95. const nsAString& aFrameOptions)
  96. {
  97. if (mUsePNG) {
  98. mContainedEncoder = new nsPNGEncoder();
  99. nsresult rv;
  100. nsAutoString noParams;
  101. rv = mContainedEncoder->InitFromData(aData, aLength, aWidth, aHeight,
  102. aStride, aInputFormat, noParams);
  103. NS_ENSURE_SUCCESS(rv, rv);
  104. uint32_t PNGImageBufferSize;
  105. mContainedEncoder->GetImageBufferUsed(&PNGImageBufferSize);
  106. mImageBufferSize = ICONFILEHEADERSIZE + ICODIRENTRYSIZE +
  107. PNGImageBufferSize;
  108. mImageBufferStart = static_cast<uint8_t*>(malloc(mImageBufferSize));
  109. if (!mImageBufferStart) {
  110. return NS_ERROR_OUT_OF_MEMORY;
  111. }
  112. mImageBufferCurr = mImageBufferStart;
  113. mICODirEntry.mBytesInRes = PNGImageBufferSize;
  114. EncodeFileHeader();
  115. EncodeInfoHeader();
  116. char* imageBuffer;
  117. rv = mContainedEncoder->GetImageBuffer(&imageBuffer);
  118. NS_ENSURE_SUCCESS(rv, rv);
  119. memcpy(mImageBufferCurr, imageBuffer, PNGImageBufferSize);
  120. mImageBufferCurr += PNGImageBufferSize;
  121. } else {
  122. mContainedEncoder = new nsBMPEncoder();
  123. nsresult rv;
  124. nsAutoString params;
  125. params.AppendLiteral("bpp=");
  126. params.AppendInt(mICODirEntry.mBitCount);
  127. rv = mContainedEncoder->InitFromData(aData, aLength, aWidth, aHeight,
  128. aStride, aInputFormat, params);
  129. NS_ENSURE_SUCCESS(rv, rv);
  130. uint32_t andMaskSize = ((GetRealWidth() + 31) / 32) * 4 * // row AND mask
  131. GetRealHeight(); // num rows
  132. uint32_t BMPImageBufferSize;
  133. mContainedEncoder->GetImageBufferUsed(&BMPImageBufferSize);
  134. mImageBufferSize = ICONFILEHEADERSIZE + ICODIRENTRYSIZE +
  135. BMPImageBufferSize + andMaskSize;
  136. mImageBufferStart = static_cast<uint8_t*>(malloc(mImageBufferSize));
  137. if (!mImageBufferStart) {
  138. return NS_ERROR_OUT_OF_MEMORY;
  139. }
  140. mImageBufferCurr = mImageBufferStart;
  141. // Icon files that wrap a BMP file must not include the BITMAPFILEHEADER
  142. // section at the beginning of the encoded BMP data, so we must skip over
  143. // bmp::FILE_HEADER_LENGTH bytes when adding the BMP content to the icon
  144. // file.
  145. mICODirEntry.mBytesInRes =
  146. BMPImageBufferSize - bmp::FILE_HEADER_LENGTH + andMaskSize;
  147. // Encode the icon headers
  148. EncodeFileHeader();
  149. EncodeInfoHeader();
  150. char* imageBuffer;
  151. rv = mContainedEncoder->GetImageBuffer(&imageBuffer);
  152. NS_ENSURE_SUCCESS(rv, rv);
  153. memcpy(mImageBufferCurr, imageBuffer + bmp::FILE_HEADER_LENGTH,
  154. BMPImageBufferSize - bmp::FILE_HEADER_LENGTH);
  155. // We need to fix the BMP height to be *2 for the AND mask
  156. uint32_t fixedHeight = GetRealHeight() * 2;
  157. NativeEndian::swapToLittleEndianInPlace(&fixedHeight, 1);
  158. // The height is stored at an offset of 8 from the DIB header
  159. memcpy(mImageBufferCurr + 8, &fixedHeight, sizeof(fixedHeight));
  160. mImageBufferCurr += BMPImageBufferSize - bmp::FILE_HEADER_LENGTH;
  161. // Calculate rowsize in DWORD's
  162. uint32_t rowSize = ((GetRealWidth() + 31) / 32) * 4; // + 31 to round up
  163. int32_t currentLine = GetRealHeight();
  164. // Write out the AND mask
  165. while (currentLine > 0) {
  166. currentLine--;
  167. uint8_t* encoded = mImageBufferCurr + currentLine * rowSize;
  168. uint8_t* encodedEnd = encoded + rowSize;
  169. while (encoded != encodedEnd) {
  170. *encoded = 0; // make everything visible
  171. encoded++;
  172. }
  173. }
  174. mImageBufferCurr += andMaskSize;
  175. }
  176. return NS_OK;
  177. }
  178. // See ::InitFromData for other info.
  179. NS_IMETHODIMP
  180. nsICOEncoder::StartImageEncode(uint32_t aWidth,
  181. uint32_t aHeight,
  182. uint32_t aInputFormat,
  183. const nsAString& aOutputOptions)
  184. {
  185. // can't initialize more than once
  186. if (mImageBufferStart || mImageBufferCurr) {
  187. return NS_ERROR_ALREADY_INITIALIZED;
  188. }
  189. // validate input format
  190. if (aInputFormat != INPUT_FORMAT_RGB &&
  191. aInputFormat != INPUT_FORMAT_RGBA &&
  192. aInputFormat != INPUT_FORMAT_HOSTARGB) {
  193. return NS_ERROR_INVALID_ARG;
  194. }
  195. // Icons are only 1 byte, so make sure our bitmap is in range
  196. if (aWidth > 256 || aHeight > 256) {
  197. return NS_ERROR_INVALID_ARG;
  198. }
  199. // parse and check any provided output options
  200. uint16_t bpp = 24;
  201. bool usePNG = true;
  202. nsresult rv = ParseOptions(aOutputOptions, bpp, usePNG);
  203. NS_ENSURE_SUCCESS(rv, rv);
  204. MOZ_ASSERT(bpp <= 32);
  205. mUsePNG = usePNG;
  206. InitFileHeader();
  207. // The width and height are stored as 0 when we have a value of 256
  208. InitInfoHeader(bpp, aWidth == 256 ? 0 : (uint8_t)aWidth,
  209. aHeight == 256 ? 0 : (uint8_t)aHeight);
  210. return NS_OK;
  211. }
  212. NS_IMETHODIMP
  213. nsICOEncoder::EndImageEncode()
  214. {
  215. // must be initialized
  216. if (!mImageBufferStart || !mImageBufferCurr) {
  217. return NS_ERROR_NOT_INITIALIZED;
  218. }
  219. mFinished = true;
  220. NotifyListener();
  221. // if output callback can't get enough memory, it will free our buffer
  222. if (!mImageBufferStart || !mImageBufferCurr) {
  223. return NS_ERROR_OUT_OF_MEMORY;
  224. }
  225. return NS_OK;
  226. }
  227. // Parses the encoder options and sets the bits per pixel to use and PNG or BMP
  228. // See InitFromData for a description of the parse options
  229. nsresult
  230. nsICOEncoder::ParseOptions(const nsAString& aOptions, uint16_t& aBppOut,
  231. bool& aUsePNGOut)
  232. {
  233. // If no parsing options just use the default of 24BPP and PNG yes
  234. if (aOptions.Length() == 0) {
  235. aUsePNGOut = true;
  236. aBppOut = 24;
  237. }
  238. // Parse the input string into a set of name/value pairs.
  239. // From format: format=<png|bmp>;bpp=<bpp_value>
  240. // to format: [0] = format=<png|bmp>, [1] = bpp=<bpp_value>
  241. nsTArray<nsCString> nameValuePairs;
  242. if (!ParseString(NS_ConvertUTF16toUTF8(aOptions), ';', nameValuePairs)) {
  243. return NS_ERROR_INVALID_ARG;
  244. }
  245. // For each name/value pair in the set
  246. for (unsigned i = 0; i < nameValuePairs.Length(); ++i) {
  247. // Split the name value pair [0] = name, [1] = value
  248. nsTArray<nsCString> nameValuePair;
  249. if (!ParseString(nameValuePairs[i], '=', nameValuePair)) {
  250. return NS_ERROR_INVALID_ARG;
  251. }
  252. if (nameValuePair.Length() != 2) {
  253. return NS_ERROR_INVALID_ARG;
  254. }
  255. // Parse the format portion of the string format=<png|bmp>;bpp=<bpp_value>
  256. if (nameValuePair[0].Equals("format",
  257. nsCaseInsensitiveCStringComparator())) {
  258. if (nameValuePair[1].Equals("png",
  259. nsCaseInsensitiveCStringComparator())) {
  260. aUsePNGOut = true;
  261. }
  262. else if (nameValuePair[1].Equals("bmp",
  263. nsCaseInsensitiveCStringComparator())) {
  264. aUsePNGOut = false;
  265. }
  266. else {
  267. return NS_ERROR_INVALID_ARG;
  268. }
  269. }
  270. // Parse the bpp portion of the string format=<png|bmp>;bpp=<bpp_value>
  271. if (nameValuePair[0].Equals("bpp", nsCaseInsensitiveCStringComparator())) {
  272. if (nameValuePair[1].EqualsLiteral("24")) {
  273. aBppOut = 24;
  274. }
  275. else if (nameValuePair[1].EqualsLiteral("32")) {
  276. aBppOut = 32;
  277. }
  278. else {
  279. return NS_ERROR_INVALID_ARG;
  280. }
  281. }
  282. }
  283. return NS_OK;
  284. }
  285. NS_IMETHODIMP
  286. nsICOEncoder::Close()
  287. {
  288. if (mImageBufferStart) {
  289. free(mImageBufferStart);
  290. mImageBufferStart = nullptr;
  291. mImageBufferSize = 0;
  292. mImageBufferReadPoint = 0;
  293. mImageBufferCurr = nullptr;
  294. }
  295. return NS_OK;
  296. }
  297. // Obtains the available bytes to read
  298. NS_IMETHODIMP
  299. nsICOEncoder::Available(uint64_t *_retval)
  300. {
  301. if (!mImageBufferStart || !mImageBufferCurr) {
  302. return NS_BASE_STREAM_CLOSED;
  303. }
  304. *_retval = GetCurrentImageBufferOffset() - mImageBufferReadPoint;
  305. return NS_OK;
  306. }
  307. // [noscript] Reads bytes which are available
  308. NS_IMETHODIMP
  309. nsICOEncoder::Read(char* aBuf, uint32_t aCount, uint32_t* _retval)
  310. {
  311. return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, _retval);
  312. }
  313. // [noscript] Reads segments
  314. NS_IMETHODIMP
  315. nsICOEncoder::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
  316. uint32_t aCount, uint32_t* _retval)
  317. {
  318. uint32_t maxCount = GetCurrentImageBufferOffset() - mImageBufferReadPoint;
  319. if (maxCount == 0) {
  320. *_retval = 0;
  321. return mFinished ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK;
  322. }
  323. if (aCount > maxCount) {
  324. aCount = maxCount;
  325. }
  326. nsresult rv = aWriter(this, aClosure,
  327. reinterpret_cast<const char*>(mImageBufferStart +
  328. mImageBufferReadPoint),
  329. 0, aCount, _retval);
  330. if (NS_SUCCEEDED(rv)) {
  331. NS_ASSERTION(*_retval <= aCount, "bad write count");
  332. mImageBufferReadPoint += *_retval;
  333. }
  334. // errors returned from the writer end here!
  335. return NS_OK;
  336. }
  337. NS_IMETHODIMP
  338. nsICOEncoder::IsNonBlocking(bool* _retval)
  339. {
  340. *_retval = true;
  341. return NS_OK;
  342. }
  343. NS_IMETHODIMP
  344. nsICOEncoder::AsyncWait(nsIInputStreamCallback* aCallback,
  345. uint32_t aFlags,
  346. uint32_t aRequestedCount,
  347. nsIEventTarget* aTarget)
  348. {
  349. if (aFlags != 0) {
  350. return NS_ERROR_NOT_IMPLEMENTED;
  351. }
  352. if (mCallback || mCallbackTarget) {
  353. return NS_ERROR_UNEXPECTED;
  354. }
  355. mCallbackTarget = aTarget;
  356. // 0 means "any number of bytes except 0"
  357. mNotifyThreshold = aRequestedCount;
  358. if (!aRequestedCount) {
  359. mNotifyThreshold = 1024; // We don't want to notify incessantly
  360. }
  361. // We set the callback absolutely last, because NotifyListener uses it to
  362. // determine if someone needs to be notified. If we don't set it last,
  363. // NotifyListener might try to fire off a notification to a null target
  364. // which will generally cause non-threadsafe objects to be used off the
  365. // main thread
  366. mCallback = aCallback;
  367. // What we are being asked for may be present already
  368. NotifyListener();
  369. return NS_OK;
  370. }
  371. NS_IMETHODIMP
  372. nsICOEncoder::CloseWithStatus(nsresult aStatus)
  373. {
  374. return Close();
  375. }
  376. void
  377. nsICOEncoder::NotifyListener()
  378. {
  379. if (mCallback &&
  380. (GetCurrentImageBufferOffset() -
  381. mImageBufferReadPoint >= mNotifyThreshold || mFinished)) {
  382. nsCOMPtr<nsIInputStreamCallback> callback;
  383. if (mCallbackTarget) {
  384. callback = NS_NewInputStreamReadyEvent(mCallback, mCallbackTarget);
  385. } else {
  386. callback = mCallback;
  387. }
  388. NS_ASSERTION(callback, "Shouldn't fail to make the callback");
  389. // Null the callback first because OnInputStreamReady could reenter
  390. // AsyncWait
  391. mCallback = nullptr;
  392. mCallbackTarget = nullptr;
  393. mNotifyThreshold = 0;
  394. callback->OnInputStreamReady(this);
  395. }
  396. }
  397. // Initializes the icon file header mICOFileHeader
  398. void
  399. nsICOEncoder::InitFileHeader()
  400. {
  401. memset(&mICOFileHeader, 0, sizeof(mICOFileHeader));
  402. mICOFileHeader.mReserved = 0;
  403. mICOFileHeader.mType = 1;
  404. mICOFileHeader.mCount = 1;
  405. }
  406. // Initializes the icon directory info header mICODirEntry
  407. void
  408. nsICOEncoder::InitInfoHeader(uint16_t aBPP, uint8_t aWidth, uint8_t aHeight)
  409. {
  410. memset(&mICODirEntry, 0, sizeof(mICODirEntry));
  411. mICODirEntry.mBitCount = aBPP;
  412. mICODirEntry.mBytesInRes = 0;
  413. mICODirEntry.mColorCount = 0;
  414. mICODirEntry.mWidth = aWidth;
  415. mICODirEntry.mHeight = aHeight;
  416. mICODirEntry.mImageOffset = ICONFILEHEADERSIZE + ICODIRENTRYSIZE;
  417. mICODirEntry.mPlanes = 1;
  418. mICODirEntry.mReserved = 0;
  419. }
  420. // Encodes the icon file header mICOFileHeader
  421. void
  422. nsICOEncoder::EncodeFileHeader()
  423. {
  424. IconFileHeader littleEndianIFH = mICOFileHeader;
  425. NativeEndian::swapToLittleEndianInPlace(&littleEndianIFH.mReserved, 1);
  426. NativeEndian::swapToLittleEndianInPlace(&littleEndianIFH.mType, 1);
  427. NativeEndian::swapToLittleEndianInPlace(&littleEndianIFH.mCount, 1);
  428. memcpy(mImageBufferCurr, &littleEndianIFH.mReserved,
  429. sizeof(littleEndianIFH.mReserved));
  430. mImageBufferCurr += sizeof(littleEndianIFH.mReserved);
  431. memcpy(mImageBufferCurr, &littleEndianIFH.mType,
  432. sizeof(littleEndianIFH.mType));
  433. mImageBufferCurr += sizeof(littleEndianIFH.mType);
  434. memcpy(mImageBufferCurr, &littleEndianIFH.mCount,
  435. sizeof(littleEndianIFH.mCount));
  436. mImageBufferCurr += sizeof(littleEndianIFH.mCount);
  437. }
  438. // Encodes the icon directory info header mICODirEntry
  439. void
  440. nsICOEncoder::EncodeInfoHeader()
  441. {
  442. IconDirEntry littleEndianmIDE = mICODirEntry;
  443. NativeEndian::swapToLittleEndianInPlace(&littleEndianmIDE.mPlanes, 1);
  444. NativeEndian::swapToLittleEndianInPlace(&littleEndianmIDE.mBitCount, 1);
  445. NativeEndian::swapToLittleEndianInPlace(&littleEndianmIDE.mBytesInRes, 1);
  446. NativeEndian::swapToLittleEndianInPlace(&littleEndianmIDE.mImageOffset, 1);
  447. memcpy(mImageBufferCurr, &littleEndianmIDE.mWidth,
  448. sizeof(littleEndianmIDE.mWidth));
  449. mImageBufferCurr += sizeof(littleEndianmIDE.mWidth);
  450. memcpy(mImageBufferCurr, &littleEndianmIDE.mHeight,
  451. sizeof(littleEndianmIDE.mHeight));
  452. mImageBufferCurr += sizeof(littleEndianmIDE.mHeight);
  453. memcpy(mImageBufferCurr, &littleEndianmIDE.mColorCount,
  454. sizeof(littleEndianmIDE.mColorCount));
  455. mImageBufferCurr += sizeof(littleEndianmIDE.mColorCount);
  456. memcpy(mImageBufferCurr, &littleEndianmIDE.mReserved,
  457. sizeof(littleEndianmIDE.mReserved));
  458. mImageBufferCurr += sizeof(littleEndianmIDE.mReserved);
  459. memcpy(mImageBufferCurr, &littleEndianmIDE.mPlanes,
  460. sizeof(littleEndianmIDE.mPlanes));
  461. mImageBufferCurr += sizeof(littleEndianmIDE.mPlanes);
  462. memcpy(mImageBufferCurr, &littleEndianmIDE.mBitCount,
  463. sizeof(littleEndianmIDE.mBitCount));
  464. mImageBufferCurr += sizeof(littleEndianmIDE.mBitCount);
  465. memcpy(mImageBufferCurr, &littleEndianmIDE.mBytesInRes,
  466. sizeof(littleEndianmIDE.mBytesInRes));
  467. mImageBufferCurr += sizeof(littleEndianmIDE.mBytesInRes);
  468. memcpy(mImageBufferCurr, &littleEndianmIDE.mImageOffset,
  469. sizeof(littleEndianmIDE.mImageOffset));
  470. mImageBufferCurr += sizeof(littleEndianmIDE.mImageOffset);
  471. }