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