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