| /* | |
| * Copyright 2023 Rive | |
| */ | |
| #include "rive/decoders/bitmap_decoder.hpp" | |
| #ifdef __APPLE__ | |
| #include "rive/rive_types.hpp" | |
| #include "rive/math/math_types.hpp" | |
| #include "rive/core/type_conversions.hpp" | |
| #include "utils/auto_cf.hpp" | |
| #include <TargetConditionals.h> | |
| #if TARGET_OS_IPHONE | |
| #include <CoreGraphics/CoreGraphics.h> | |
| #include <ImageIO/ImageIO.h> | |
| #elif TARGET_OS_MAC | |
| #include <ApplicationServices/ApplicationServices.h> | |
| #endif | |
| #endif | |
| #include <stdio.h> | |
| #include <string.h> | |
| #include <vector> | |
| #ifdef RIVE_PNG | |
| std::unique_ptr<Bitmap> DecodePng(const uint8_t bytes[], size_t byteCount); | |
| #endif | |
| #ifdef RIVE_JPEG | |
| std::unique_ptr<Bitmap> DecodeJpeg(const uint8_t bytes[], size_t byteCount); | |
| #endif | |
| #ifdef RIVE_WEBP | |
| std::unique_ptr<Bitmap> DecodeWebP(const uint8_t bytes[], size_t byteCount); | |
| #endif | |
| static Bitmap::ImageFormat _formats[] = { | |
| { | |
| "png", | |
| Bitmap::ImageType::png, | |
| {0x89, 0x50, 0x4E, 0x47}, | |
| #ifdef RIVE_PNG | |
| DecodePng, | |
| #else | |
| nullptr, | |
| #endif | |
| }, | |
| { | |
| "jpeg", | |
| Bitmap::ImageType::jpeg, | |
| {0xFF, 0xD8, 0xFF}, | |
| #ifdef RIVE_JPEG | |
| DecodeJpeg, | |
| #else | |
| nullptr, | |
| #endif | |
| }, | |
| { | |
| "webp", | |
| Bitmap::ImageType::webp, | |
| {0x52, 0x49, 0x46}, | |
| #ifdef RIVE_WEBP | |
| DecodeWebP, | |
| #else | |
| nullptr, | |
| #endif | |
| }, | |
| }; | |
| const Bitmap::ImageFormat* Bitmap::RecognizeImageFormat(const uint8_t bytes[], | |
| size_t byteCount) | |
| { | |
| for (const ImageFormat& format : _formats) | |
| { | |
| auto& fingerprint = format.fingerprint; | |
| // Immediately discard decoders with fingerprints that are longer than | |
| // the file buffer. | |
| if (format.fingerprint.size() > byteCount) | |
| { | |
| continue; | |
| } | |
| // If the fingerprint doesn't match, discrd this decoder. These are all | |
| // bytes so .size() is fine here. | |
| if (memcmp(fingerprint.data(), bytes, fingerprint.size()) != 0) | |
| { | |
| continue; | |
| } | |
| return &format; | |
| } | |
| return nullptr; | |
| } | |
| #ifdef __APPLE__ | |
| // Represents raw, premultiplied, RGBA image data with tightly packed rows | |
| // (width * 4 bytes). | |
| struct PlatformCGImage | |
| { | |
| uint32_t width = 0; | |
| uint32_t height = 0; | |
| bool opaque = false; | |
| std::unique_ptr<uint8_t[]> pixels; | |
| }; | |
| bool cg_image_decode(const uint8_t* encodedBytes, | |
| size_t encodedSizeInBytes, | |
| PlatformCGImage* platformImage) | |
| { | |
| AutoCF data = | |
| CFDataCreate(kCFAllocatorDefault, encodedBytes, encodedSizeInBytes); | |
| if (!data) | |
| { | |
| return false; | |
| } | |
| AutoCF source = CGImageSourceCreateWithData(data, nullptr); | |
| if (!source) | |
| { | |
| return false; | |
| } | |
| AutoCF image = CGImageSourceCreateImageAtIndex(source, 0, nullptr); | |
| if (!image) | |
| { | |
| return false; | |
| } | |
| bool isOpaque = false; | |
| switch (CGImageGetAlphaInfo(image.get())) | |
| { | |
| case kCGImageAlphaNone: | |
| case kCGImageAlphaNoneSkipFirst: | |
| case kCGImageAlphaNoneSkipLast: | |
| isOpaque = true; | |
| break; | |
| default: | |
| break; | |
| } | |
| const size_t width = CGImageGetWidth(image); | |
| const size_t height = CGImageGetHeight(image); | |
| const size_t rowBytes = width * 4; // 4 bytes per pixel | |
| const size_t size = rowBytes * height; | |
| const size_t bitsPerComponent = 8; | |
| CGBitmapInfo cgInfo = kCGBitmapByteOrder32Big; // rgba | |
| if (isOpaque) | |
| { | |
| cgInfo |= kCGImageAlphaNoneSkipLast; | |
| } | |
| else | |
| { | |
| cgInfo |= kCGImageAlphaPremultipliedLast; | |
| } | |
| std::unique_ptr<uint8_t[]> pixels(new uint8_t[size]); | |
| AutoCF cs = CGColorSpaceCreateDeviceRGB(); | |
| AutoCF cg = CGBitmapContextCreate(pixels.get(), | |
| width, | |
| height, | |
| bitsPerComponent, | |
| rowBytes, | |
| cs, | |
| cgInfo); | |
| if (!cg) | |
| { | |
| return false; | |
| } | |
| CGContextSetBlendMode(cg, kCGBlendModeCopy); | |
| CGContextDrawImage(cg, CGRectMake(0, 0, width, height), image); | |
| platformImage->width = rive::castTo<uint32_t>(width); | |
| platformImage->height = rive::castTo<uint32_t>(height); | |
| platformImage->opaque = isOpaque; | |
| platformImage->pixels = std::move(pixels); | |
| return true; | |
| } | |
| std::unique_ptr<Bitmap> Bitmap::decode(const uint8_t bytes[], size_t byteCount) | |
| { | |
| PlatformCGImage image; | |
| if (!cg_image_decode(bytes, byteCount, &image)) | |
| { | |
| #if defined RIVE_APPLETVOS || defined RIVE_APPLETVOS_SIMULATOR | |
| const Bitmap::ImageFormat* format = | |
| Bitmap::RecognizeImageFormat(bytes, byteCount); | |
| if (format != nullptr) | |
| { | |
| auto bitmap = format->decodeImage != nullptr | |
| ? format->decodeImage(bytes, byteCount) | |
| : nullptr; | |
| return bitmap; | |
| } | |
| #endif | |
| return nullptr; | |
| } | |
| return std::make_unique<Bitmap>(image.width, | |
| image.height, | |
| image.width * image.height * 4, | |
| // CG always premultiplies alpha. | |
| PixelFormat::RGBAPremul, | |
| std::move(image.pixels)); | |
| } | |
| #else | |
| std::unique_ptr<Bitmap> Bitmap::decode(const uint8_t bytes[], size_t byteCount) | |
| { | |
| const ImageFormat* format = RecognizeImageFormat(bytes, byteCount); | |
| if (format != nullptr) | |
| { | |
| auto bitmap = format->decodeImage != nullptr | |
| ? format->decodeImage(bytes, byteCount) | |
| : nullptr; | |
| if (!bitmap) | |
| { | |
| fprintf(stderr, | |
| "Bitmap::decode - failed to decode a %s.\n", | |
| format->name); | |
| } | |
| return bitmap; | |
| } | |
| return nullptr; | |
| } | |
| #endif |