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