SkCodec::Result SkBmpCodec::ReadHeader()

in src/codec/SkBmpCodec.cpp [148:599]


SkCodec::Result SkBmpCodec::ReadHeader(SkStream* stream, bool inIco,
        std::unique_ptr<SkCodec>* codecOut) {
    // The total bytes in the bmp file
    // We only need to use this value for RLE decoding, so we will only
    // check that it is valid in the RLE case.
    uint32_t totalBytes;
    // The offset from the start of the file where the pixel data begins
    uint32_t offset;
    // The size of the second (info) header in bytes
    uint32_t infoBytes;

    // Bmps embedded in Icos skip the first Bmp header
    if (!inIco) {
        // Read the first header and the size of the second header
        uint8_t hBuffer[kBmpHeaderBytesPlusFour];
        if (stream->read(hBuffer, kBmpHeaderBytesPlusFour) !=
                kBmpHeaderBytesPlusFour) {
            SkCodecPrintf("Error: unable to read first bitmap header.\n");
            return kIncompleteInput;
        }

        totalBytes = SkCodecPriv::UnsafeGetInt(hBuffer, 2);
        offset = SkCodecPriv::UnsafeGetInt(hBuffer, 10);
        if (offset < kBmpHeaderBytes + kBmpOS2V1Bytes) {
            SkCodecPrintf("Error: invalid starting location for pixel data\n");
            return kInvalidInput;
        }

        // The size of the second (info) header in bytes
        // The size is the first field of the second header, so we have already
        // read the first four infoBytes.
        infoBytes = SkCodecPriv::UnsafeGetInt(hBuffer, 14);
        if (infoBytes < kBmpOS2V1Bytes) {
            SkCodecPrintf("Error: invalid second header size.\n");
            return kInvalidInput;
        }
    } else {
        // This value is only used by RLE compression.  Bmp in Ico files do not
        // use RLE.  If the compression field is incorrectly signaled as RLE,
        // we will catch this and signal an error below.
        totalBytes = 0;

        // Bmps in Ico cannot specify an offset.  We will always assume that
        // pixel data begins immediately after the color table.  This value
        // will be corrected below.
        offset = 0;

        // Read the size of the second header
        uint8_t hBuffer[4];
        if (stream->read(hBuffer, 4) != 4) {
            SkCodecPrintf("Error: unable to read size of second bitmap header.\n");
            return kIncompleteInput;
        }
        infoBytes = SkCodecPriv::UnsafeGetInt(hBuffer, 0);
        if (infoBytes < kBmpOS2V1Bytes) {
            SkCodecPrintf("Error: invalid second header size.\n");
            return kInvalidInput;
        }
    }

    // Determine image information depending on second header format
    const BmpHeaderType headerType = get_header_type(infoBytes);
    if (kUnknown_BmpHeaderType == headerType) {
        return kInvalidInput;
    }

    // We already read the first four bytes of the info header to get the size
    const uint32_t infoBytesRemaining = infoBytes - 4;

    // Read the second header
    std::unique_ptr<uint8_t[]> iBuffer(new uint8_t[infoBytesRemaining]);
    if (stream->read(iBuffer.get(), infoBytesRemaining) != infoBytesRemaining) {
        SkCodecPrintf("Error: unable to read second bitmap header.\n");
        return kIncompleteInput;
    }

    // The number of bits used per pixel in the pixel data
    uint16_t bitsPerPixel;

    // The compression method for the pixel data
    uint32_t compression = kNone_BmpCompressionMethod;

    // Number of colors in the color table, defaults to 0 or max (see below)
    uint32_t numColors = 0;

    // Bytes per color in the color table, early versions use 3, most use 4
    uint32_t bytesPerColor;

    // The image width and height
    int width, height;

    switch (headerType) {
        case kInfoV1_BmpHeaderType:
        case kInfoV2_BmpHeaderType:
        case kInfoV3_BmpHeaderType:
        case kInfoV4_BmpHeaderType:
        case kInfoV5_BmpHeaderType:
        case kOS2VX_BmpHeaderType:
            // We check the size of the header before entering the if statement.
            // We should not reach this point unless the size is large enough for
            // these required fields.
            SkASSERT(infoBytesRemaining >= 12);
            width = SkCodecPriv::UnsafeGetInt(iBuffer.get(), 0);
            height = SkCodecPriv::UnsafeGetInt(iBuffer.get(), 4);
            bitsPerPixel = SkCodecPriv::UnsafeGetShort(iBuffer.get(), 10);

            // Some versions do not have these fields, so we check before
            // overwriting the default value.
            if (infoBytesRemaining >= 16) {
                compression = SkCodecPriv::UnsafeGetInt(iBuffer.get(), 12);
                if (infoBytesRemaining >= 32) {
                    numColors = SkCodecPriv::UnsafeGetInt(iBuffer.get(), 28);
                }
            }

            // All of the headers that reach this point, store color table entries
            // using 4 bytes per pixel.
            bytesPerColor = 4;
            break;
        case kOS2V1_BmpHeaderType:
            // The OS2V1 is treated separately because it has a unique format
            width = (int)SkCodecPriv::UnsafeGetShort(iBuffer.get(), 0);
            height = (int)SkCodecPriv::UnsafeGetShort(iBuffer.get(), 2);
            bitsPerPixel = SkCodecPriv::UnsafeGetShort(iBuffer.get(), 6);
            bytesPerColor = 3;
            break;
        case kUnknown_BmpHeaderType:
            // We'll exit above in this case.
            SkASSERT(false);
            return kInvalidInput;
    }

    // Check for valid dimensions from header
    SkCodec::SkScanlineOrder rowOrder = SkCodec::kBottomUp_SkScanlineOrder;
    if (height < 0) {
        // We can't negate INT32_MIN.
        if (height == INT32_MIN) {
            return kInvalidInput;
        }

        height = -height;
        rowOrder = SkCodec::kTopDown_SkScanlineOrder;
    }
    // The height field for bmp in ico is double the actual height because they
    // contain an XOR mask followed by an AND mask
    if (inIco) {
        height /= 2;
    }

    // Arbitrary maximum. Matches Chromium.
    constexpr int kMaxDim = 1 << 16;
    if (width <= 0 || height <= 0 || width >= kMaxDim || height >= kMaxDim) {
        SkCodecPrintf("Error: invalid bitmap dimensions.\n");
        return kInvalidInput;
    }

    // Create mask struct
    SkMasks::InputMasks inputMasks;
    memset(&inputMasks, 0, sizeof(SkMasks::InputMasks));

    // Determine the input compression format and set bit masks if necessary
    uint32_t maskBytes = 0;
    BmpInputFormat inputFormat = kUnknown_BmpInputFormat;
    switch (compression) {
        case kNone_BmpCompressionMethod:
            inputFormat = kStandard_BmpInputFormat;

            // In addition to more standard pixel compression formats, bmp supports
            // the use of bit masks to determine pixel components.  The standard
            // format for representing 16-bit colors is 555 (XRRRRRGGGGGBBBBB),
            // which does not map well to any Skia color formats.  For this reason,
            // we will always enable mask mode with 16 bits per pixel.
            if (16 == bitsPerPixel) {
                inputMasks.red = 0x7C00;
                inputMasks.green = 0x03E0;
                inputMasks.blue = 0x001F;
                inputFormat = kBitMask_BmpInputFormat;
            }
            break;
        case k8BitRLE_BmpCompressionMethod:
            if (bitsPerPixel != 8) {
                SkCodecPrintf("Warning: correcting invalid bitmap format.\n");
                bitsPerPixel = 8;
            }
            inputFormat = kRLE_BmpInputFormat;
            break;
        case k4BitRLE_BmpCompressionMethod:
            if (bitsPerPixel != 4) {
                SkCodecPrintf("Warning: correcting invalid bitmap format.\n");
                bitsPerPixel = 4;
            }
            inputFormat = kRLE_BmpInputFormat;
            break;
        case kAlphaBitMasks_BmpCompressionMethod:
        case kBitMasks_BmpCompressionMethod:
            // Load the masks
            inputFormat = kBitMask_BmpInputFormat;
            switch (headerType) {
                case kInfoV1_BmpHeaderType: {
                    // The V1 header stores the bit masks after the header
                    uint8_t buffer[kBmpMaskBytes];
                    if (stream->read(buffer, kBmpMaskBytes) != kBmpMaskBytes) {
                        SkCodecPrintf("Error: unable to read bit inputMasks.\n");
                        return kIncompleteInput;
                    }
                    maskBytes = kBmpMaskBytes;
                    inputMasks.red = SkCodecPriv::UnsafeGetInt(buffer, 0);
                    inputMasks.green = SkCodecPriv::UnsafeGetInt(buffer, 4);
                    inputMasks.blue = SkCodecPriv::UnsafeGetInt(buffer, 8);
                    break;
                }
                case kInfoV2_BmpHeaderType:
                case kInfoV3_BmpHeaderType:
                case kInfoV4_BmpHeaderType:
                case kInfoV5_BmpHeaderType:
                    // Header types are matched based on size.  If the header
                    // is V2+, we are guaranteed to be able to read at least
                    // this size.
                    SkASSERT(infoBytesRemaining >= 48);
                    inputMasks.red = SkCodecPriv::UnsafeGetInt(iBuffer.get(), 36);
                    inputMasks.green = SkCodecPriv::UnsafeGetInt(iBuffer.get(), 40);
                    inputMasks.blue = SkCodecPriv::UnsafeGetInt(iBuffer.get(), 44);

                    if (kInfoV2_BmpHeaderType == headerType ||
                            (kInfoV3_BmpHeaderType == headerType && !inIco)) {
                        break;
                    }

                    // V3+ bmp files introduce an alpha mask and allow the creator of the image
                    // to use the alpha channels.  However, many of these images leave the
                    // alpha channel blank and expect to be rendered as opaque.  This is the
                    // case for almost all V3 images, so we ignore the alpha mask.  For V4+
                    // images in kMask mode, we will use the alpha mask.  Additionally, V3
                    // bmp-in-ico expect us to use the alpha mask.
                    //
                    // skbug.com/4116: We should perhaps also apply the alpha mask in kStandard
                    //                 mode.  We just haven't seen any images that expect this
                    //                 behavior.
                    //
                    // Header types are matched based on size.  If the header is
                    // V3+, we are guaranteed to be able to read at least this size.
                    SkASSERT(infoBytesRemaining >= 52);
                    inputMasks.alpha = SkCodecPriv::UnsafeGetInt(iBuffer.get(), 48);
                    break;
                case kOS2VX_BmpHeaderType:
                    // TODO: Decide if we intend to support this.
                    //       It is unsupported in the previous version and
                    //       in chromium.  I have not come across a test case
                    //       that uses this format.
                    SkCodecPrintf("Error: huffman format unsupported.\n");
                    return kUnimplemented;
                default:
                   SkCodecPrintf("Error: invalid bmp bit masks header.\n");
                   return kInvalidInput;
            }
            break;
        case kJpeg_BmpCompressionMethod:
            if (24 == bitsPerPixel) {
                inputFormat = kRLE_BmpInputFormat;
                break;
            }
            [[fallthrough]];
        case kPng_BmpCompressionMethod:
            // TODO: Decide if we intend to support this.
            //       It is unsupported in the previous version and
            //       in chromium.  I think it is used mostly for printers.
            SkCodecPrintf("Error: compression format not supported.\n");
            return kUnimplemented;
        case kCMYK_BmpCompressionMethod:
        case kCMYK8BitRLE_BmpCompressionMethod:
        case kCMYK4BitRLE_BmpCompressionMethod:
            // TODO: Same as above.
            SkCodecPrintf("Error: CMYK not supported for bitmap decoding.\n");
            return kUnimplemented;
        default:
            SkCodecPrintf("Error: invalid format for bitmap decoding.\n");
            return kInvalidInput;
    }
    iBuffer.reset();

    // Calculate the number of bytes read so far
    const uint32_t bytesRead = kBmpHeaderBytes + infoBytes + maskBytes;
    if (!inIco && offset < bytesRead) {
        // TODO (msarett): Do we really want to fail if the offset in the header is invalid?
        //                 Seems like we can just assume that the offset is zero and try to decode?
        //                 Maybe we don't want to try to decode corrupt images?
        SkCodecPrintf("Error: pixel data offset less than header size.\n");
        return kInvalidInput;
    }



    switch (inputFormat) {
        case kStandard_BmpInputFormat: {
            // BMPs are generally opaque, however BMPs-in-ICOs may contain
            // a transparency mask after the image.  Therefore, we mark the
            // alpha as kBinary if the BMP is contained in an ICO.
            // We use |isOpaque| to indicate if the BMP itself is opaque.
            SkEncodedInfo::Alpha alpha = inIco ? SkEncodedInfo::kBinary_Alpha :
                    SkEncodedInfo::kOpaque_Alpha;
            bool isOpaque = true;

            SkEncodedInfo::Color color;
            uint8_t bitsPerComponent;
            switch (bitsPerPixel) {
                // Palette formats
                case 1:
                case 2:
                case 4:
                case 8:
                    // In the case of ICO, kBGRA is actually the closest match,
                    // since we will need to apply a transparency mask.
                    if (inIco) {
                        color = SkEncodedInfo::kBGRA_Color;
                        bitsPerComponent = 8;
                    } else {
                        color = SkEncodedInfo::kPalette_Color;
                        bitsPerComponent = (uint8_t) bitsPerPixel;
                    }
                    break;
                case 24:
                    // In the case of ICO, kBGRA is actually the closest match,
                    // since we will need to apply a transparency mask.
                    color = inIco ? SkEncodedInfo::kBGRA_Color : SkEncodedInfo::kBGR_Color;
                    bitsPerComponent = 8;
                    break;
                case 32:
                    // 32-bit BMP-in-ICOs actually use the alpha channel in place of a
                    // transparency mask.
                    if (inIco) {
                        isOpaque = false;
                        alpha = SkEncodedInfo::kUnpremul_Alpha;
                        color = SkEncodedInfo::kBGRA_Color;
                    } else {
                        color = SkEncodedInfo::kBGRX_Color;
                    }
                    bitsPerComponent = 8;
                    break;
                default:
                    SkCodecPrintf("Error: invalid input value for bits per pixel.\n");
                    return kInvalidInput;
            }

            if (codecOut) {
                // We require streams to have a memory base for Bmp-in-Ico decodes.
                SkASSERT(!inIco || nullptr != stream->getMemoryBase());

                // Set the image info and create a codec.
                auto info = SkEncodedInfo::Make(width, height, color, alpha, bitsPerComponent);
                *codecOut = std::make_unique<SkBmpStandardCodec>(std::move(info),
                                                       std::unique_ptr<SkStream>(stream),
                                                       bitsPerPixel, numColors, bytesPerColor,
                                                       offset - bytesRead, rowOrder, isOpaque,
                                                       inIco);
                return static_cast<SkBmpStandardCodec*>(codecOut->get())->didCreateSrcBuffer()
                        ? kSuccess : kInvalidInput;
            }
            return kSuccess;
        }

        case kBitMask_BmpInputFormat: {
            // Bmp-in-Ico must be standard mode
            if (inIco) {
                SkCodecPrintf("Error: Icos may not use bit mask format.\n");
                return kInvalidInput;
            }

            switch (bitsPerPixel) {
                case 16:
                case 24:
                case 32:
                    break;
                default:
                    SkCodecPrintf("Error: invalid input value for bits per pixel.\n");
                    return kInvalidInput;
            }

            // Skip to the start of the pixel array.
            // We can do this here because there is no color table to read
            // in bit mask mode.
            if (stream->skip(offset - bytesRead) != offset - bytesRead) {
                SkCodecPrintf("Error: unable to skip to image data.\n");
                return kIncompleteInput;
            }

            if (codecOut) {
                // Check that input bit masks are valid and create the masks object
                SkASSERT(bitsPerPixel % 8 == 0);
                std::unique_ptr<SkMasks> masks(SkMasks::CreateMasks(inputMasks, bitsPerPixel/8));
                if (nullptr == masks) {
                    SkCodecPrintf("Error: invalid input masks.\n");
                    return kInvalidInput;
                }

                // Masked bmps are not a great fit for SkEncodedInfo, since they have
                // arbitrary component orderings and bits per component.  Here we choose
                // somewhat reasonable values - it's ok that we don't match exactly
                // because SkBmpMaskCodec has its own mask swizzler anyway.
                SkEncodedInfo::Color color;
                SkEncodedInfo::Alpha alpha;
                if (masks->getAlphaMask()) {
                    color = SkEncodedInfo::kBGRA_Color;
                    alpha = SkEncodedInfo::kUnpremul_Alpha;
                } else {
                    color = SkEncodedInfo::kBGR_Color;
                    alpha = SkEncodedInfo::kOpaque_Alpha;
                }
                auto info = SkEncodedInfo::Make(width, height, color, alpha, 8);
                *codecOut = std::make_unique<SkBmpMaskCodec>(std::move(info),
                                                   std::unique_ptr<SkStream>(stream), bitsPerPixel,
                                                   masks.release(), rowOrder);
                return static_cast<SkBmpMaskCodec*>(codecOut->get())->didCreateSrcBuffer()
                        ? kSuccess : kInvalidInput;
            }
            return kSuccess;
        }

        case kRLE_BmpInputFormat: {
            // We should not reach this point without a valid value of bitsPerPixel.
            SkASSERT(4 == bitsPerPixel || 8 == bitsPerPixel || 24 == bitsPerPixel);

            // Check for a valid number of total bytes when in RLE mode
            if (totalBytes <= offset) {
                SkCodecPrintf("Error: RLE requires valid input size.\n");
                return kInvalidInput;
            }

            // Bmp-in-Ico must be standard mode
            // When inIco is true, this line cannot be reached, since we
            // require that RLE Bmps have a valid number of totalBytes, and
            // Icos skip the header that contains totalBytes.
            SkASSERT(!inIco);

            if (codecOut) {
                // RLE inputs may skip pixels, leaving them as transparent.  This
                // is uncommon, but we cannot be certain that an RLE bmp will be
                // opaque or that we will be able to represent it with a palette.
                // For that reason, we always indicate that we are kBGRA.
                auto info = SkEncodedInfo::Make(width, height, SkEncodedInfo::kBGRA_Color,
                                                SkEncodedInfo::kBinary_Alpha, 8);
                *codecOut = std::make_unique<SkBmpRLECodec>(std::move(info),
                                                  std::unique_ptr<SkStream>(stream), bitsPerPixel,
                                                  numColors, bytesPerColor, offset - bytesRead,
                                                  rowOrder);
            }
            return kSuccess;
        }
        default:
            SkASSERT(false);
            return kInvalidInput;
    }
}