/* * Copyright 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include "ultrahdr/ultrahdrcommon.h" #include "ultrahdr/gainmapmath.h" #include "ultrahdr/jpegr.h" using namespace ultrahdr; // Color gamuts for image data, sync with ultrahdr.h const int kCgMin = ULTRAHDR_COLORGAMUT_UNSPECIFIED; const int kCgMax = ULTRAHDR_COLORGAMUT_BT2100; // Transfer functions for image data, sync with ultrahdr.h const int kTfMin = ULTRAHDR_TF_UNSPECIFIED; const int kTfMax = ULTRAHDR_TF_SRGB; // Transfer functions for image data, sync with ultrahdr.h const int kOfMin = ULTRAHDR_OUTPUT_UNSPECIFIED; const int kOfMax = ULTRAHDR_OUTPUT_HDR_HLG; // quality factor const int kQfMin = -10; const int kQfMax = 110; class UltraHdrEncFuzzer { public: UltraHdrEncFuzzer(const uint8_t* data, size_t size) : mFdp(data, size) {}; void process(); template void fillBuffer(T* data, int width, int height, int stride); private: FuzzedDataProvider mFdp; }; template void UltraHdrEncFuzzer::fillBuffer(T* data, int width, int height, int stride) { if (!mFdp.remaining_bytes()) return; T* tmp = data; std::vector buffer(width); for (int i = 0; i < buffer.size(); i++) { buffer[i] = mFdp.ConsumeIntegral(); } for (int j = 0; j < height; j++) { for (int i = 0; i < width; i += buffer.size()) { memcpy(tmp + i, buffer.data(), std::min((int)buffer.size(), (width - i)) * sizeof(*data)); std::shuffle(buffer.begin(), buffer.end(), std::default_random_engine(std::random_device{}())); } tmp += stride; } } void UltraHdrEncFuzzer::process() { if (mFdp.remaining_bytes()) { struct jpegr_uncompressed_struct p010Img{}; struct jpegr_uncompressed_struct yuv420Img{}; struct jpegr_uncompressed_struct grayImg{}; struct jpegr_compressed_struct jpegImgR{}; struct jpegr_compressed_struct jpegImg{}; struct jpegr_compressed_struct jpegGainMap{}; // which encode api to select int muxSwitch = mFdp.ConsumeIntegralInRange(0, 4); // quality factor int quality = mFdp.ConsumeIntegralInRange(kQfMin, kQfMax); // hdr_tf auto tf = static_cast(mFdp.ConsumeIntegralInRange(kTfMin, kTfMax)); // p010 Cg auto p010Cg = static_cast(mFdp.ConsumeIntegralInRange(kCgMin, kCgMax)); // 420 Cg auto yuv420Cg = static_cast(mFdp.ConsumeIntegralInRange(kCgMin, kCgMax)); // hdr_of auto of = static_cast(mFdp.ConsumeIntegralInRange(kOfMin, kOfMax)); int width = mFdp.ConsumeIntegralInRange(kMinWidth, kMaxWidth); width = (width >> 1) << 1; int height = mFdp.ConsumeIntegralInRange(kMinHeight, kMaxHeight); height = (height >> 1) << 1; // gain_map quality factor auto gainmap_quality = mFdp.ConsumeIntegral(); // multi channel gainmap auto multi_channel_gainmap = mFdp.ConsumeIntegral(); // gainmap scale factor auto gm_scale_factor = mFdp.ConsumeIntegralInRange(-32, 192); // encoding speed preset auto enc_preset = mFdp.ConsumeBool() ? UHDR_USAGE_REALTIME : UHDR_USAGE_BEST_QUALITY; // gainmap metadata auto minBoost = mFdp.ConsumeFloatingPointInRange(-4.0f, 64.0f); auto maxBoost = mFdp.ConsumeFloatingPointInRange(-4.0f, 64.0f); auto gamma = mFdp.ConsumeFloatingPointInRange(-1.0f, 5); auto offsetSdr = mFdp.ConsumeFloatingPointInRange(-1.0f, 1.0f); auto offsetHdr = mFdp.ConsumeFloatingPointInRange(-1.0f, 1.0f); auto minCapacity = mFdp.ConsumeFloatingPointInRange(-4.0f, 48.0f); auto maxCapacity = mFdp.ConsumeFloatingPointInRange(-4.0f, 48.0f); // target display peak brightness auto targetDispPeakBrightness = mFdp.ConsumeFloatingPointInRange(100.0f, 10500.0f); // raw buffer config bool hasP010Stride = mFdp.ConsumeBool(); size_t yP010Stride = mFdp.ConsumeIntegralInRange(width, width + 128); if (!hasP010Stride) yP010Stride = width; bool isP010UVContiguous = mFdp.ConsumeBool(); bool hasP010UVStride = mFdp.ConsumeBool(); size_t uvP010Stride = mFdp.ConsumeIntegralInRange(width, width + 128); if (!hasP010UVStride) uvP010Stride = width; bool hasYuv420Stride = mFdp.ConsumeBool(); size_t yYuv420Stride = mFdp.ConsumeIntegralInRange(width, width + 128); if (!hasYuv420Stride) yYuv420Stride = width; bool isYuv420UVContiguous = mFdp.ConsumeBool(); bool hasYuv420UVStride = mFdp.ConsumeBool(); size_t uvYuv420Stride = mFdp.ConsumeIntegralInRange(width / 2, width / 2 + 128); if (!hasYuv420UVStride) uvYuv420Stride = width / 2; // display boost float displayBoost = mFdp.ConsumeFloatingPointInRange(1.0, FLT_MAX); std::unique_ptr bufferYHdr = nullptr; std::unique_ptr bufferUVHdr = nullptr; std::unique_ptr bufferYSdr = nullptr; std::unique_ptr bufferUVSdr = nullptr; std::unique_ptr grayImgRaw = nullptr; if (muxSwitch != 4) { // init p010 image p010Img.width = width; p010Img.height = height; p010Img.colorGamut = p010Cg; p010Img.luma_stride = yP010Stride; if (isP010UVContiguous) { size_t p010Size = yP010Stride * height * 3 / 2; bufferYHdr = std::make_unique(p010Size); p010Img.data = bufferYHdr.get(); p010Img.chroma_data = nullptr; p010Img.chroma_stride = 0; fillBuffer(bufferYHdr.get(), width, height, yP010Stride); fillBuffer(bufferYHdr.get() + yP010Stride * height, width, height / 2, yP010Stride); } else { size_t p010YSize = yP010Stride * height; bufferYHdr = std::make_unique(p010YSize); p010Img.data = bufferYHdr.get(); fillBuffer(bufferYHdr.get(), width, height, yP010Stride); size_t p010UVSize = uvP010Stride * p010Img.height / 2; bufferUVHdr = std::make_unique(p010UVSize); p010Img.chroma_data = bufferUVHdr.get(); p010Img.chroma_stride = uvP010Stride; fillBuffer(bufferUVHdr.get(), width, height / 2, uvP010Stride); } } else { size_t map_width = width / kMapDimensionScaleFactorDefault; size_t map_height = height / kMapDimensionScaleFactorDefault; // init 400 image grayImg.width = map_width; grayImg.height = map_height; grayImg.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED; const size_t graySize = map_width * map_height; grayImgRaw = std::make_unique(graySize); grayImg.data = grayImgRaw.get(); fillBuffer(grayImgRaw.get(), map_width, map_height, map_width); grayImg.chroma_data = nullptr; grayImg.luma_stride = 0; grayImg.chroma_stride = 0; } if (muxSwitch > 0) { // init 420 image yuv420Img.width = width; yuv420Img.height = height; yuv420Img.colorGamut = yuv420Cg; yuv420Img.luma_stride = yYuv420Stride; if (isYuv420UVContiguous) { size_t yuv420Size = yYuv420Stride * height * 3 / 2; bufferYSdr = std::make_unique(yuv420Size); yuv420Img.data = bufferYSdr.get(); yuv420Img.chroma_data = nullptr; yuv420Img.chroma_stride = 0; fillBuffer(bufferYSdr.get(), width, height, yYuv420Stride); fillBuffer(bufferYSdr.get() + yYuv420Stride * height, width / 2, height / 2, yYuv420Stride / 2); fillBuffer(bufferYSdr.get() + yYuv420Stride * height * 5 / 4, width / 2, height / 2, yYuv420Stride / 2); } else { size_t yuv420YSize = yYuv420Stride * height; bufferYSdr = std::make_unique(yuv420YSize); yuv420Img.data = bufferYSdr.get(); fillBuffer(bufferYSdr.get(), width, height, yYuv420Stride); size_t yuv420UVSize = uvYuv420Stride * yuv420Img.height / 2 * 2; bufferUVSdr = std::make_unique(yuv420UVSize); yuv420Img.chroma_data = bufferUVSdr.get(); yuv420Img.chroma_stride = uvYuv420Stride; fillBuffer(bufferUVSdr.get(), width / 2, height / 2, uvYuv420Stride); fillBuffer(bufferUVSdr.get() + uvYuv420Stride * height / 2, width / 2, height / 2, uvYuv420Stride); } } // dest // 2 * p010 size as input data is random, DCT compression might not behave as expected jpegImgR.maxLength = std::max(8 * 1024 /* min size 8kb */, width * height * 3 * 2); auto jpegImgRaw = std::make_unique(jpegImgR.maxLength); jpegImgR.data = jpegImgRaw.get(); // #define DUMP_PARAM #ifdef DUMP_PARAM std::cout << "Api Select " << muxSwitch << std::endl; std::cout << "image dimensions " << width << " x " << height << std::endl; std::cout << "p010 color gamut " << p010Img.colorGamut << std::endl; std::cout << "p010 luma stride " << p010Img.luma_stride << std::endl; std::cout << "p010 chroma stride " << p010Img.chroma_stride << std::endl; std::cout << "420 color gamut " << yuv420Img.colorGamut << std::endl; std::cout << "420 luma stride " << yuv420Img.luma_stride << std::endl; std::cout << "420 chroma stride " << yuv420Img.chroma_stride << std::endl; std::cout << "quality factor " << quality << std::endl; #endif JpegR jpegHdr(nullptr, gm_scale_factor, gainmap_quality, multi_channel_gainmap, gamma, enc_preset, minBoost, maxBoost, targetDispPeakBrightness); status_t status = JPEGR_UNKNOWN_ERROR; if (muxSwitch == 0) { // api 0 jpegImgR.length = 0; status = jpegHdr.encodeJPEGR(&p010Img, tf, &jpegImgR, quality, nullptr); } else if (muxSwitch == 1) { // api 1 jpegImgR.length = 0; status = jpegHdr.encodeJPEGR(&p010Img, &yuv420Img, tf, &jpegImgR, quality, nullptr); } else { // compressed img JpegEncoderHelper encoder; struct jpegr_uncompressed_struct yuv420ImgCopy = yuv420Img; if (yuv420ImgCopy.luma_stride == 0) yuv420ImgCopy.luma_stride = yuv420Img.width; if (!yuv420ImgCopy.chroma_data) { uint8_t* data = reinterpret_cast(yuv420Img.data); yuv420ImgCopy.chroma_data = data + yuv420Img.luma_stride * yuv420Img.height; yuv420ImgCopy.chroma_stride = yuv420Img.luma_stride >> 1; } const uint8_t* planes[3]{reinterpret_cast(yuv420ImgCopy.data), reinterpret_cast(yuv420ImgCopy.chroma_data), reinterpret_cast(yuv420ImgCopy.chroma_data) + yuv420ImgCopy.chroma_stride * yuv420ImgCopy.height / 2}; const unsigned int strides[3]{yuv420ImgCopy.luma_stride, yuv420ImgCopy.chroma_stride, yuv420ImgCopy.chroma_stride}; if (encoder .compressImage(planes, strides, yuv420ImgCopy.width, yuv420ImgCopy.height, UHDR_IMG_FMT_12bppYCbCr420, quality, nullptr, 0) .error_code == UHDR_CODEC_OK) { jpegImg.length = encoder.getCompressedImageSize(); jpegImg.maxLength = jpegImg.length; jpegImg.data = encoder.getCompressedImagePtr(); jpegImg.colorGamut = yuv420Cg; if (muxSwitch == 2) { // api 2 jpegImgR.length = 0; status = jpegHdr.encodeJPEGR(&p010Img, &yuv420Img, &jpegImg, tf, &jpegImgR); } else if (muxSwitch == 3) { // api 3 jpegImgR.length = 0; status = jpegHdr.encodeJPEGR(&p010Img, &jpegImg, tf, &jpegImgR); } else if (muxSwitch == 4) { // api 4 jpegImgR.length = 0; JpegEncoderHelper gainMapEncoder; const uint8_t* planeGm[1]{reinterpret_cast(grayImg.data)}; const unsigned int strideGm[1]{grayImg.width}; if (gainMapEncoder .compressImage(planeGm, strideGm, grayImg.width, grayImg.height, UHDR_IMG_FMT_8bppYCbCr400, quality, nullptr, 0) .error_code == UHDR_CODEC_OK) { jpegGainMap.length = gainMapEncoder.getCompressedImageSize(); jpegGainMap.maxLength = jpegImg.length; jpegGainMap.data = gainMapEncoder.getCompressedImagePtr(); jpegGainMap.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED; ultrahdr_metadata_struct metadata; metadata.version = kJpegrVersion; metadata.maxContentBoost = maxBoost; metadata.minContentBoost = minBoost; metadata.gamma = gamma; metadata.offsetSdr = offsetSdr; metadata.offsetHdr = offsetHdr; metadata.hdrCapacityMin = minCapacity; metadata.hdrCapacityMax = maxCapacity; status = jpegHdr.encodeJPEGR(&jpegImg, &jpegGainMap, &metadata, &jpegImgR); } } } } if (status == JPEGR_NO_ERROR) { jpegr_info_struct info{}; status = jpegHdr.getJPEGRInfo(&jpegImgR, &info); if (status == JPEGR_NO_ERROR) { size_t outSize = info.width * info.height * ((of == ULTRAHDR_OUTPUT_HDR_LINEAR) ? 8 : 4); jpegr_uncompressed_struct decodedJpegR; auto decodedRaw = std::make_unique(outSize); decodedJpegR.data = decodedRaw.get(); ultrahdr_metadata_struct metadata; status = jpegHdr.decodeJPEGR(&jpegImgR, &decodedJpegR, displayBoost, nullptr, of, nullptr, &metadata); if (status != JPEGR_NO_ERROR) { ALOGE("encountered error during decoding %d", status); } } else { ALOGE("encountered error during get jpeg info %d", status); } } else { ALOGE("encountered error during encoding %d", status); } } } extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { UltraHdrEncFuzzer fuzzHandle(data, size); fuzzHandle.process(); return 0; }