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