| /* -*- C++ -*- |
| * File: sonycc.cpp |
| * Copyright (C) 2024-2025 Alex Tutubalin, LibRaw LLC |
| * |
| Sony YCC (small/medium lossy compressed) decoder |
| |
| Code partially backported from DNGLab's rust code |
| DNGLab was originally created in 2021 by Daniel Vogelbacher. |
| |
| LibRaw is free software; you can redistribute it and/or modify |
| it under the terms of the one of two licenses as you choose: |
| |
| 1. GNU LESSER GENERAL PUBLIC LICENSE version 2.1 |
| (See file LICENSE.LGPL provided in LibRaw distribution archive for details). |
| |
| 2. COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0 |
| (See file LICENSE.CDDL provided in LibRaw distribution archive for details). |
| |
| */ |
| |
| #include "third_party/libraw/internal/libraw_cxx_defs.h" |
| #include "third_party/libraw/internal/losslessjpeg.h" |
| #include <vector> |
| #include <algorithm> |
| |
| #define ifp libraw_internal_data.internal_data.input |
| #define UD libraw_internal_data.unpacker_data |
| #define S imgdata.sizes |
| |
| struct LibRaw_SonyYCC_Decompressor : public LibRaw_LjpegDecompressor |
| { |
| LibRaw_SonyYCC_Decompressor(uint8_t *b, unsigned s): LibRaw_LjpegDecompressor(b,s){} |
| bool decode_sony(std::vector<uint16_t> &dest, int width, int height); |
| bool decode_sony_ljpeg_420(std::vector<uint16_t> &dest, int width, int height); |
| |
| }; |
| |
| static |
| #ifdef _MSC_VER |
| __forceinline |
| #else |
| inline |
| #endif |
| void copy_yuv_420(uint16_t *out, uint32_t row, uint32_t col, uint32_t width, |
| int32_t y1, int32_t y2, int32_t y3, int32_t y4, int32_t cb, int32_t cr) |
| { |
| uint32_t pix1 = row * width + col; |
| uint32_t pix2 = pix1 + 3; |
| uint32_t pix3 = (row + 1) * width + col; |
| uint32_t pix4 = pix3 + 3; |
| |
| out[pix1 + 0] = uint16_t(y1); |
| out[pix1 + 1] = uint16_t(cb); |
| out[pix1 + 2] = uint16_t(cr); |
| out[pix2 + 0] = uint16_t(y2); |
| out[pix2 + 1] = uint16_t(cb); |
| out[pix2 + 2] = uint16_t(cr); |
| out[pix3 + 0] = uint16_t(y3); |
| out[pix3 + 1] = uint16_t(cb); |
| out[pix3 + 2] = uint16_t(cr); |
| out[pix4 + 0] = uint16_t(y4); |
| out[pix4 + 1] = uint16_t(cb); |
| out[pix4 + 2] = uint16_t(cr); |
| } |
| |
| |
| bool LibRaw_SonyYCC_Decompressor::decode_sony_ljpeg_420(std::vector<uint16_t> &_dest, int width, int height) |
| { |
| if (sof.width * 3 != unsigned(width) || sof.height != unsigned(height)) |
| return false; |
| if (width % 2 || width % 6 || height % 2) |
| return false; |
| if (_dest.size() < size_t(width*height)) |
| return false; |
| |
| HuffTable &huff1 = dhts[sof.components[0].dc_tbl]; |
| HuffTable &huff2 = dhts[sof.components[1].dc_tbl]; |
| HuffTable &huff3 = dhts[sof.components[2].dc_tbl]; |
| |
| if (!huff1.initialized || !huff2.initialized || !huff3.initialized) |
| return false; |
| |
| BitPumpJpeg pump(buffer); |
| |
| int32_t base = 1 << (sof.precision - point_transform - 1); |
| int32_t y1 = base + huff1.decode(pump); |
| int32_t y2 = y1 + huff1.decode(pump); |
| int32_t y3 = y1 + huff1.decode(pump); |
| int32_t y4 = y3 + huff1.decode(pump); |
| |
| int32_t cb = base + huff2.decode(pump); |
| int32_t cr = base + huff3.decode(pump); |
| |
| uint16_t *dest = _dest.data(); |
| copy_yuv_420(dest, 0, 0, width, y1, y2, y3, y4, cb, cr); |
| |
| for (int32_t row = 0; row < height; row += 2) |
| { |
| int32_t startcol = row == 0 ? 6 : 0; |
| for (int32_t col = startcol; col < width; col += 6) |
| { |
| int32_t py1, py3, pcb, pcr; |
| if (col == 0) |
| { |
| uint32_t pos = (row - 2) * width; |
| py1 = dest[pos]; |
| py3 = 0; |
| pcb = dest[pos + 1]; |
| pcr = dest[pos + 2]; |
| } |
| else |
| { |
| uint32_t pos1 = row * width + col - 3; |
| uint32_t pos3 = (row + 1) * width + col - 3; |
| py1 = dest[pos1]; |
| py3 = dest[pos3]; |
| pcb = dest[pos1 + 1]; |
| pcr = dest[pos1 + 2]; |
| }; |
| y1 = py1 + huff1.decode(pump); |
| y2 = y1 + huff1.decode(pump); |
| y3 = ((col == 0) ? y1 : py3) + huff1.decode(pump); |
| y4 = y3 + huff1.decode(pump); |
| cb = pcb + huff2.decode(pump); |
| cr = pcr + huff3.decode(pump); |
| copy_yuv_420(dest, row, col, width, y1, y2, y3, y4, cb, cr); |
| } |
| } |
| return true; |
| } |
| |
| bool LibRaw_SonyYCC_Decompressor::decode_sony(std::vector<uint16_t> &dest, int width, int height) |
| { |
| if (sof.components[0].subsample_h == 2 && sof.components[0].subsample_v == 2) |
| { |
| return decode_sony_ljpeg_420(dest, width, height); |
| } |
| else if (sof.components[0].subsample_h == 2 && sof.components[0].subsample_v == 1) |
| return decode_ljpeg_422(dest, width, height); |
| else |
| return false; |
| } |
| |
| static |
| #ifdef _MSC_VER |
| __forceinline |
| #else |
| inline |
| #endif |
| void copy_ycc(ushort dst[][4], int rawwidth, int rawheight, int destrow0, int destcol0, ushort *src, |
| int srcwidth, // full array width is 3x |
| int srcheight, int steph, int stepv) |
| { |
| if (steph < 2 && stepv < 2) |
| { |
| for (int tilerow = 0; tilerow < srcheight && destrow0 + tilerow < rawheight; tilerow++) |
| { |
| ushort(*destrow)[4] = &dst[(destrow0 + tilerow) * rawwidth + destcol0]; |
| for (int tilecol = 0; tilecol < srcwidth && tilecol + destcol0 < rawwidth; tilecol++) |
| { |
| int pix = (tilerow * srcwidth + tilecol) * 3; |
| destrow[tilecol][0] = src[pix]; |
| destrow[tilecol][1] = src[pix + 1] > 8192 ? src[pix + 1] - 8192 : 0; |
| destrow[tilecol][2] = src[pix + 2] > 8192 ? src[pix + 2] - 8192 : 0; |
| } |
| } |
| } |
| else |
| { |
| for (int tilerow = 0; tilerow < srcheight && destrow0 + tilerow < rawheight; tilerow++) |
| { |
| int destrow = destrow0 + tilerow; |
| ushort(*dest)[4] = &dst[destrow * rawwidth + destcol0]; |
| for (int tilecol = 0; tilecol < srcwidth && tilecol + destcol0 < rawwidth; tilecol++) |
| { |
| int pix = (tilerow * srcwidth + tilecol) * 3; |
| int destcol = destcol0 + tilecol; |
| dest[tilecol][0] = src[pix]; |
| if((destrow%stepv) == 0 && (destcol%steph)==0) |
| { |
| dest[tilecol][1] = src[pix + 1] > 8192 ? src[pix + 1] - 8192 : 0; |
| dest[tilecol][2] = src[pix + 2] > 8192 ? src[pix + 2] - 8192 : 0; |
| } |
| } |
| } |
| |
| } |
| } |
| |
| static |
| #ifdef _MSC_VER |
| __forceinline |
| #else |
| inline |
| #endif |
| uint16_t _lim16bit(float q) |
| { |
| if (q < 0.f) |
| q = 0.f; |
| else if (q > 65535.f) |
| q = 65535.f; |
| return uint16_t(uint32_t(q)); |
| } |
| |
| static |
| #ifdef _MSC_VER |
| __forceinline |
| #else |
| inline |
| #endif |
| void ycc2rgb(uint16_t dst[][4], int rawwidth, int rawheight, int destrow0, int destcol0, ushort *src, |
| int srcwidth, // full array width is 3x |
| int srcheight) |
| { |
| const ushort cdelta = 16383; |
| for (int tilerow = 0; tilerow < srcheight && destrow0 + tilerow < rawheight; tilerow++) |
| { |
| ushort(*destrow)[4] = &dst[(destrow0 + tilerow) * rawwidth + destcol0]; |
| for (int tilecol = 0; tilecol < srcwidth && tilecol + destcol0 < rawwidth; tilecol++) |
| { |
| int pix = (tilerow * srcwidth + tilecol) * 3; |
| float Y = float(src[pix]); |
| float Cb = float(src[pix + 1] - cdelta); |
| float Cr = float(src[pix + 2] - cdelta); |
| float R = Y + 1.40200f * Cr; |
| float G = Y - 0.34414f * Cb - 0.71414f * Cr; |
| float B = Y + 1.77200f * Cb; |
| destrow[tilecol][0] = _lim16bit(R); |
| destrow[tilecol][1] = _lim16bit(G); |
| destrow[tilecol][2] = _lim16bit(B); |
| } |
| } |
| } |
| |
| void LibRaw::sony_ycbcr_load_raw() |
| { |
| if (!imgdata.image) |
| throw LIBRAW_EXCEPTION_IO_CORRUPT; |
| // Sony YCC RAWs are always tiled |
| if (UD.tile_width < 1 || UD.tile_width > S.raw_width) |
| throw LIBRAW_EXCEPTION_IO_CORRUPT; |
| if (UD.tile_length < 1 || UD.tile_length > S.raw_height) |
| throw LIBRAW_EXCEPTION_IO_CORRUPT; |
| |
| // calculate tile count |
| int tile_w = (S.raw_width + UD.tile_width - 1) / UD.tile_width; |
| int tile_h = (S.raw_height + UD.tile_length - 1) / UD.tile_length; |
| int tiles = tile_w * tile_h; |
| if (tiles < 1 || tiles > 1024) |
| throw LIBRAW_EXCEPTION_IO_CORRUPT; |
| |
| INT64 fsize = ifp->size(); |
| std::vector<INT64> toffsets(tiles); |
| ifp->seek(UD.data_offset, SEEK_SET); // We're already here, but to make sure |
| for (int i = 0; i < tiles; i++) |
| toffsets[i] = get4(); |
| |
| std::vector<unsigned> tlengths(tiles); |
| ifp->seek(UD.data_size, SEEK_SET); |
| for (int i = 0; i < tiles; i++) |
| { |
| tlengths[i] = get4(); |
| if(toffsets[i]+tlengths[i] > fsize) |
| throw LIBRAW_EXCEPTION_IO_CORRUPT; |
| } |
| |
| INT64 tilesize = INT64(UD.tile_width) * INT64(UD.tile_length) * 3LL; |
| |
| if (tilesize > 2048LL * 1024LL * 1024LL) // Sony tiles are 512x512, so >2Gb decoded tile is definitely broken file. |
| throw LIBRAW_EXCEPTION_IO_CORRUPT; |
| |
| if (tilesize > INT64(imgdata.rawparams.max_raw_memory_mb) * INT64(1024 * 1024)) // check against memory size limit |
| throw LIBRAW_EXCEPTION_ALLOC; |
| |
| unsigned maxcomprlen = *std::max_element(tlengths.begin(), tlengths.end()); |
| |
| if (INT64(maxcomprlen) > 1024LL * 1024LL * 1024LL) // Sony tiles are 512x512, so >1Gb compressed tile is definitely broken file. |
| throw LIBRAW_EXCEPTION_IO_CORRUPT; |
| |
| if (INT64(maxcomprlen) > INT64(imgdata.rawparams.max_raw_memory_mb) * INT64(1024 * 1024)) // check against memory size limit |
| throw LIBRAW_EXCEPTION_ALLOC; |
| |
| std::vector<uint8_t> iobuffer(maxcomprlen+4u); // Extra four bytes to ensure LJPEG byte stream marker search is ok and bit buffer fast lookups are also ok |
| std::vector<uint16_t> tilebuffer; |
| for (int tile = 0; tile < tiles; tile++) |
| { |
| ifp->seek(toffsets[tile],SEEK_SET); |
| int readed = ifp->read(iobuffer.data(), 1, tlengths[tile]); |
| if(unsigned(readed) != tlengths[tile]) |
| throw LIBRAW_EXCEPTION_IO_EOF; |
| LibRaw_SonyYCC_Decompressor dec(iobuffer.data(), readed); |
| if(dec.sof.cps != 3) // !YUV |
| throw LIBRAW_EXCEPTION_IO_CORRUPT; |
| if(dec.state != LibRaw_LjpegDecompressor::State::OK) |
| throw LIBRAW_EXCEPTION_IO_CORRUPT; |
| |
| unsigned tiledatatsize = UD.tile_width * UD.tile_length * 3; |
| if (tilebuffer.size() < tiledatatsize) |
| tilebuffer.resize(tiledatatsize); |
| |
| if(!dec.decode_sony(tilebuffer, UD.tile_width * 3, UD.tile_length)) |
| throw LIBRAW_EXCEPTION_IO_CORRUPT; |
| |
| int tilerow = tile / tile_w; |
| int tilecol = tile % tile_w; |
| if (imgdata.rawparams.specials & LIBRAW_RAWSPECIAL_SRAW_NO_RGB) |
| { |
| if(imgdata.rawparams.specials & LIBRAW_RAWSPECIAL_SRAW_NO_INTERPOLATE) |
| copy_ycc(imgdata.image, S.raw_width, S.raw_height, tilerow * UD.tile_length, tilecol * UD.tile_width, |
| tilebuffer.data(), UD.tile_width, UD.tile_length, dec.sof.components[0].subsample_h, dec.sof.components[0].subsample_v); |
| else |
| copy_ycc(imgdata.image, S.raw_width, S.raw_height, tilerow * UD.tile_length, tilecol * UD.tile_width, |
| tilebuffer.data(), UD.tile_width, UD.tile_length, 1, 1); |
| } |
| else |
| ycc2rgb(imgdata.image, S.raw_width, S.raw_height, tilerow * UD.tile_length, tilecol * UD.tile_width, |
| tilebuffer.data(), UD.tile_width, UD.tile_length); |
| } |
| |
| for (int i = 0; i < 6; i++) |
| imgdata.color.cblack[i] = 0; |
| |
| if (imgdata.rawparams.specials & LIBRAW_RAWSPECIAL_SRAW_NO_RGB) |
| { |
| imgdata.color.maximum = 18091; // estimated on over-exposed ISO100 frame |
| imgdata.color.black = 0; |
| } |
| else |
| { |
| imgdata.color.maximum = 17536; // estimated on over-exposed ISO100 frame |
| imgdata.color.black = 1024; |
| } |
| } |