| /* |
| * 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 <fuzzer/FuzzedDataProvider.h> |
| #include <algorithm> |
| #include <iostream> |
| #include <memory> |
| #include <random> |
| |
| #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 <typename T> |
| void fillBuffer(T* data, int width, int height, int stride); |
| |
| private: |
| FuzzedDataProvider mFdp; |
| }; |
| |
| template <typename T> |
| void UltraHdrEncFuzzer::fillBuffer(T* data, int width, int height, int stride) { |
| if (!mFdp.remaining_bytes()) return; |
| |
| T* tmp = data; |
| std::vector<T> buffer(width); |
| for (int i = 0; i < buffer.size(); i++) { |
| buffer[i] = mFdp.ConsumeIntegral<T>(); |
| } |
| 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<int>(0, 4); |
| |
| // quality factor |
| int quality = mFdp.ConsumeIntegralInRange<int>(kQfMin, kQfMax); |
| |
| // hdr_tf |
| auto tf = |
| static_cast<ultrahdr_transfer_function>(mFdp.ConsumeIntegralInRange<int>(kTfMin, kTfMax)); |
| |
| // p010 Cg |
| auto p010Cg = |
| static_cast<ultrahdr_color_gamut>(mFdp.ConsumeIntegralInRange<int>(kCgMin, kCgMax)); |
| |
| // 420 Cg |
| auto yuv420Cg = |
| static_cast<ultrahdr_color_gamut>(mFdp.ConsumeIntegralInRange<int>(kCgMin, kCgMax)); |
| |
| // hdr_of |
| auto of = static_cast<ultrahdr_output_format>(mFdp.ConsumeIntegralInRange<int>(kOfMin, kOfMax)); |
| |
| int width = mFdp.ConsumeIntegralInRange<int>(kMinWidth, kMaxWidth); |
| width = (width >> 1) << 1; |
| |
| int height = mFdp.ConsumeIntegralInRange<int>(kMinHeight, kMaxHeight); |
| height = (height >> 1) << 1; |
| |
| // gain_map quality factor |
| auto gainmap_quality = mFdp.ConsumeIntegral<int8_t>(); |
| |
| // multi channel gainmap |
| auto multi_channel_gainmap = mFdp.ConsumeIntegral<int8_t>(); |
| |
| // gainmap scale factor |
| auto gm_scale_factor = mFdp.ConsumeIntegralInRange<int16_t>(-32, 192); |
| |
| // encoding speed preset |
| auto enc_preset = mFdp.ConsumeBool() ? UHDR_USAGE_REALTIME : UHDR_USAGE_BEST_QUALITY; |
| |
| // gainmap metadata |
| auto minBoost = mFdp.ConsumeFloatingPointInRange<float>(-4.0f, 64.0f); |
| auto maxBoost = mFdp.ConsumeFloatingPointInRange<float>(-4.0f, 64.0f); |
| auto gamma = mFdp.ConsumeFloatingPointInRange<float>(-1.0f, 5); |
| auto offsetSdr = mFdp.ConsumeFloatingPointInRange<float>(-1.0f, 1.0f); |
| auto offsetHdr = mFdp.ConsumeFloatingPointInRange<float>(-1.0f, 1.0f); |
| auto minCapacity = mFdp.ConsumeFloatingPointInRange<float>(-4.0f, 48.0f); |
| auto maxCapacity = mFdp.ConsumeFloatingPointInRange<float>(-4.0f, 48.0f); |
| |
| // target display peak brightness |
| auto targetDispPeakBrightness = mFdp.ConsumeFloatingPointInRange<float>(100.0f, 10500.0f); |
| |
| // raw buffer config |
| bool hasP010Stride = mFdp.ConsumeBool(); |
| size_t yP010Stride = mFdp.ConsumeIntegralInRange<uint16_t>(width, width + 128); |
| if (!hasP010Stride) yP010Stride = width; |
| bool isP010UVContiguous = mFdp.ConsumeBool(); |
| bool hasP010UVStride = mFdp.ConsumeBool(); |
| size_t uvP010Stride = mFdp.ConsumeIntegralInRange<uint16_t>(width, width + 128); |
| if (!hasP010UVStride) uvP010Stride = width; |
| |
| bool hasYuv420Stride = mFdp.ConsumeBool(); |
| size_t yYuv420Stride = mFdp.ConsumeIntegralInRange<uint16_t>(width, width + 128); |
| if (!hasYuv420Stride) yYuv420Stride = width; |
| bool isYuv420UVContiguous = mFdp.ConsumeBool(); |
| bool hasYuv420UVStride = mFdp.ConsumeBool(); |
| size_t uvYuv420Stride = mFdp.ConsumeIntegralInRange<uint16_t>(width / 2, width / 2 + 128); |
| if (!hasYuv420UVStride) uvYuv420Stride = width / 2; |
| |
| // display boost |
| float displayBoost = mFdp.ConsumeFloatingPointInRange<float>(1.0, FLT_MAX); |
| |
| std::unique_ptr<uint16_t[]> bufferYHdr = nullptr; |
| std::unique_ptr<uint16_t[]> bufferUVHdr = nullptr; |
| std::unique_ptr<uint8_t[]> bufferYSdr = nullptr; |
| std::unique_ptr<uint8_t[]> bufferUVSdr = nullptr; |
| std::unique_ptr<uint8_t[]> 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<uint16_t[]>(p010Size); |
| p010Img.data = bufferYHdr.get(); |
| p010Img.chroma_data = nullptr; |
| p010Img.chroma_stride = 0; |
| fillBuffer<uint16_t>(bufferYHdr.get(), width, height, yP010Stride); |
| fillBuffer<uint16_t>(bufferYHdr.get() + yP010Stride * height, width, height / 2, |
| yP010Stride); |
| } else { |
| size_t p010YSize = yP010Stride * height; |
| bufferYHdr = std::make_unique<uint16_t[]>(p010YSize); |
| p010Img.data = bufferYHdr.get(); |
| fillBuffer<uint16_t>(bufferYHdr.get(), width, height, yP010Stride); |
| size_t p010UVSize = uvP010Stride * p010Img.height / 2; |
| bufferUVHdr = std::make_unique<uint16_t[]>(p010UVSize); |
| p010Img.chroma_data = bufferUVHdr.get(); |
| p010Img.chroma_stride = uvP010Stride; |
| fillBuffer<uint16_t>(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<uint8_t[]>(graySize); |
| grayImg.data = grayImgRaw.get(); |
| fillBuffer<uint8_t>(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<uint8_t[]>(yuv420Size); |
| yuv420Img.data = bufferYSdr.get(); |
| yuv420Img.chroma_data = nullptr; |
| yuv420Img.chroma_stride = 0; |
| fillBuffer<uint8_t>(bufferYSdr.get(), width, height, yYuv420Stride); |
| fillBuffer<uint8_t>(bufferYSdr.get() + yYuv420Stride * height, width / 2, height / 2, |
| yYuv420Stride / 2); |
| fillBuffer<uint8_t>(bufferYSdr.get() + yYuv420Stride * height * 5 / 4, width / 2, |
| height / 2, yYuv420Stride / 2); |
| } else { |
| size_t yuv420YSize = yYuv420Stride * height; |
| bufferYSdr = std::make_unique<uint8_t[]>(yuv420YSize); |
| yuv420Img.data = bufferYSdr.get(); |
| fillBuffer<uint8_t>(bufferYSdr.get(), width, height, yYuv420Stride); |
| size_t yuv420UVSize = uvYuv420Stride * yuv420Img.height / 2 * 2; |
| bufferUVSdr = std::make_unique<uint8_t[]>(yuv420UVSize); |
| yuv420Img.chroma_data = bufferUVSdr.get(); |
| yuv420Img.chroma_stride = uvYuv420Stride; |
| fillBuffer<uint8_t>(bufferUVSdr.get(), width / 2, height / 2, uvYuv420Stride); |
| fillBuffer<uint8_t>(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<uint8_t[]>(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<uint8_t*>(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<uint8_t*>(yuv420ImgCopy.data), |
| reinterpret_cast<uint8_t*>(yuv420ImgCopy.chroma_data), |
| reinterpret_cast<uint8_t*>(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<uint8_t*>(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<uint8_t[]>(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; |
| } |