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