|
30 | 30 | #include "io/Buffer.h" |
31 | 31 | #include "io/ByteStream.h" |
32 | 32 | #include <algorithm> |
33 | | -#include <array> |
34 | 33 | #include <cstdint> |
35 | 34 | #include <cstring> |
36 | 35 | #include <iterator> |
37 | 36 | #include <limits> |
38 | | -#include <memory> |
39 | 37 | #include <vector> |
40 | 38 |
|
41 | | -using std::copy_n; |
42 | | - |
43 | 39 | namespace rawspeed { |
44 | 40 |
|
| 41 | +namespace { |
| 42 | + |
| 43 | +using PerCompRecipeVec = std::vector<LJpegDecompressor::PerComponentRecipe>; |
| 44 | + |
| 45 | +struct ScanSettings final { |
| 46 | + RawImage raw; |
| 47 | + iRectangle2D imgFrame; |
| 48 | + iPoint2D jpegFrameDim; |
| 49 | + iPoint2D maxRes; |
| 50 | + LJpegDecompressor::DecodeSettings decode; |
| 51 | + Array1DRef<const uint8_t> input; |
| 52 | +}; |
| 53 | + |
| 54 | +[[nodiscard]] int |
| 55 | +getNumLJpegRowsPerRestartInterval(uint32_t numMCUsPerRestartInterval, |
| 56 | + iPoint2D jpegFrameDim) { |
| 57 | + if (numMCUsPerRestartInterval == 0) |
| 58 | + return jpegFrameDim.y; |
| 59 | + |
| 60 | + const int numMCUsPerRow = jpegFrameDim.x; |
| 61 | + if (numMCUsPerRestartInterval % numMCUsPerRow != 0) |
| 62 | + ThrowRDE("Restart interval is not a multiple of frame row size"); |
| 63 | + return implicit_cast<int>(numMCUsPerRestartInterval) / numMCUsPerRow; |
| 64 | +} |
| 65 | + |
| 66 | +[[nodiscard]] iPoint2D getMaxResolution(const RawImage& raw, iPoint2D maxDim, |
| 67 | + int numComponents, |
| 68 | + iPoint2D jpegFrameDim) { |
| 69 | + if (implicit_cast<int64_t>(maxDim.x) * implicit_cast<int>(raw->getCpp()) > |
| 70 | + std::numeric_limits<int>::max()) |
| 71 | + ThrowRDE("Maximal output tile is too large"); |
| 72 | + |
| 73 | + const auto maxRes = |
| 74 | + iPoint2D(implicit_cast<int>(raw->getCpp()) * maxDim.x, maxDim.y); |
| 75 | + if (maxRes.area() != numComponents * jpegFrameDim.area()) |
| 76 | + ThrowRDE("LJpeg frame area does not match maximal tile area"); |
| 77 | + |
| 78 | + return maxRes; |
| 79 | +} |
| 80 | + |
| 81 | +[[nodiscard]] iPoint2D |
| 82 | +getStandardMCUSize(int numComponents, iPoint2D jpegFrameDim, iPoint2D maxRes) { |
| 83 | + if (jpegFrameDim.x > maxRes.x) |
| 84 | + return {}; |
| 85 | + |
| 86 | + if (maxRes.x % jpegFrameDim.x != 0 || maxRes.y % jpegFrameDim.y != 0) |
| 87 | + ThrowRDE("Maximal output tile size is not a multiple of LJpeg frame size"); |
| 88 | + |
| 89 | + const auto mcuSize = |
| 90 | + iPoint2D{maxRes.x / jpegFrameDim.x, maxRes.y / jpegFrameDim.y}; |
| 91 | + if (mcuSize.area() != implicit_cast<uint64_t>(numComponents)) |
| 92 | + ThrowRDE("Unexpected MCU size, does not match LJpeg component count"); |
| 93 | + |
| 94 | + if (mcuSize.x < mcuSize.y) |
| 95 | + return {}; |
| 96 | + |
| 97 | + return mcuSize; |
| 98 | +} |
| 99 | + |
| 100 | +[[nodiscard]] ByteStream::size_type |
| 101 | +decodeStandardScan(const ScanSettings& settings, iPoint2D mcuSize, |
| 102 | + const PerCompRecipeVec& rec) { |
| 103 | + const LJpegDecompressor::Frame jpegFrame = {mcuSize, settings.jpegFrameDim}; |
| 104 | + LJpegDecompressor d(settings.raw, settings.imgFrame, jpegFrame, rec, |
| 105 | + settings.decode, settings.input); |
| 106 | + return d.decode(); |
| 107 | +} |
| 108 | + |
| 109 | +void copyDeinterleavedRows(const RawImage& raw, RawImage tmpRaw, uint32_t offX, |
| 110 | + uint32_t offY, uint32_t tileWidth, int widthPack) { |
| 111 | + const auto tmpData = tmpRaw->getU16DataAsUncroppedArray2DRef(); |
| 112 | + const auto outData = raw->getU16DataAsUncroppedArray2DRef(); |
| 113 | + |
| 114 | + const auto cpp = implicit_cast<int>(raw->getCpp()); |
| 115 | + const int outRowPixels = cpp * implicit_cast<int>(tileWidth); |
| 116 | + |
| 117 | + for (int jpegRow = 0; jpegRow < tmpRaw->dim.y; ++jpegRow) { |
| 118 | + for (int pack = 0; pack < widthPack; ++pack) { |
| 119 | + const int tileRow = implicit_cast<int>(offY) + jpegRow * widthPack + pack; |
| 120 | + if (tileRow >= raw->dim.y) |
| 121 | + continue; |
| 122 | + |
| 123 | + const int srcCol = pack * outRowPixels; |
| 124 | + const int dstCol = cpp * implicit_cast<int>(offX); |
| 125 | + std::memcpy(&outData(tileRow, dstCol), &tmpData(jpegRow, srcCol), |
| 126 | + sizeof(uint16_t) * outRowPixels); |
| 127 | + } |
| 128 | + } |
| 129 | +} |
| 130 | + |
| 131 | +[[nodiscard]] ByteStream::size_type |
| 132 | +decodeInvertedScan(const ScanSettings& settings, int numComponents, |
| 133 | + uint32_t offX, uint32_t offY, uint32_t tileWidth, |
| 134 | + const PerCompRecipeVec& rec) { |
| 135 | + const int effectiveJpegWidth = settings.jpegFrameDim.x * numComponents; |
| 136 | + |
| 137 | + if (effectiveJpegWidth % settings.maxRes.x != 0) |
| 138 | + ThrowRDE("Effective JPEG width is not a multiple of tile width"); |
| 139 | + if (settings.maxRes.y % settings.jpegFrameDim.y != 0) |
| 140 | + ThrowRDE("Tile height is not a multiple of LJpeg frame height"); |
| 141 | + |
| 142 | + const int widthPack = effectiveJpegWidth / settings.maxRes.x; |
| 143 | + if (widthPack * settings.jpegFrameDim.y != settings.maxRes.y) |
| 144 | + ThrowRDE("Inverted reshape dimensions mismatch"); |
| 145 | + if (widthPack < 1 || widthPack > 4) |
| 146 | + ThrowRDE("Unexpected row packing factor: %d", widthPack); |
| 147 | + |
| 148 | + const auto mcuSize = iPoint2D{numComponents, 1}; |
| 149 | + RawImage tmpRaw = |
| 150 | + RawImage::create(iPoint2D(effectiveJpegWidth, settings.jpegFrameDim.y), |
| 151 | + RawImageType::UINT16, 1); |
| 152 | + const iRectangle2D tmpFrame = {{0, 0}, |
| 153 | + {effectiveJpegWidth, settings.jpegFrameDim.y}}; |
| 154 | + const LJpegDecompressor::Frame jpegFrame = {mcuSize, settings.jpegFrameDim}; |
| 155 | + |
| 156 | + LJpegDecompressor d(tmpRaw, tmpFrame, jpegFrame, rec, settings.decode, |
| 157 | + settings.input); |
| 158 | + const auto consumed = d.decode(); |
| 159 | + |
| 160 | + copyDeinterleavedRows(settings.raw, tmpRaw, offX, offY, tileWidth, widthPack); |
| 161 | + return consumed; |
| 162 | +} |
| 163 | + |
| 164 | +} // namespace |
| 165 | + |
45 | 166 | LJpegDecoder::LJpegDecoder(ByteStream bs, const RawImage& img) |
46 | 167 | : AbstractLJpegDecoder(bs, img) { |
47 | 168 | if (mRaw->getDataType() != RawImageType::UINT16) |
@@ -113,149 +234,37 @@ Buffer::size_type LJpegDecoder::decodeScan() { |
113 | 234 | if (frame.compInfo[i].superH != 1 || frame.compInfo[i].superV != 1) |
114 | 235 | ThrowRDE("Unsupported subsampling"); |
115 | 236 |
|
116 | | - const int N_COMP = frame.cps; |
| 237 | + const int numComponents = frame.cps; |
117 | 238 |
|
118 | | - std::vector<LJpegDecompressor::PerComponentRecipe> rec; |
119 | | - rec.reserve(N_COMP); |
120 | | - std::generate_n(std::back_inserter(rec), N_COMP, |
121 | | - [&rec, hts = getPrefixCodeDecoders(N_COMP), |
122 | | - initPred = getInitialPredictors( |
123 | | - N_COMP)]() -> LJpegDecompressor::PerComponentRecipe { |
| 239 | + PerCompRecipeVec rec; |
| 240 | + rec.reserve(numComponents); |
| 241 | + std::generate_n(std::back_inserter(rec), numComponents, |
| 242 | + [&rec, hts = getPrefixCodeDecoders(numComponents), |
| 243 | + initPred = getInitialPredictors(numComponents)]() |
| 244 | + -> LJpegDecompressor::PerComponentRecipe { |
124 | 245 | const auto i = implicit_cast<int>(rec.size()); |
125 | 246 | return {*hts[i], initPred[i]}; |
126 | 247 | }); |
127 | 248 |
|
128 | 249 | const auto jpegFrameDim = iPoint2D(frame.w, frame.h); |
129 | | - |
130 | | - if (implicit_cast<int64_t>(maxDim.x) * implicit_cast<int>(mRaw->getCpp()) > |
131 | | - std::numeric_limits<int>::max()) |
132 | | - ThrowRDE("Maximal output tile is too large"); |
133 | | - |
134 | 250 | const auto maxRes = |
135 | | - iPoint2D(implicit_cast<int>(mRaw->getCpp()) * maxDim.x, maxDim.y); |
136 | | - if (maxRes.area() != N_COMP * jpegFrameDim.area()) |
137 | | - ThrowRDE("LJpeg frame area does not match maximal tile area"); |
138 | | - |
139 | | - // Detect whether the JPEG frame uses an inverted reshape (e.g. DJI/Blackmagic |
140 | | - // CinemaDNG): JPEG frame is wider than tile and shorter, with packed rows. |
141 | | - // Standard (Adobe): maxRes.x >= jpegFrameDim.x (tile is wider/equal) |
142 | | - // Inverted (DJI): jpegFrameDim.x > maxRes.x (JPEG frame is wider) |
143 | | - const bool invertedReshape = (jpegFrameDim.x > maxRes.x); |
144 | | - if (!invertedReshape) { |
145 | | - // Standard case: tile width is a multiple of JPEG frame width. |
146 | | - if (maxRes.x % jpegFrameDim.x != 0 || maxRes.y % jpegFrameDim.y != 0) |
147 | | - ThrowRDE( |
148 | | - "Maximal output tile size is not a multiple of LJpeg frame size"); |
149 | | - |
150 | | - const auto MCUSize = |
151 | | - iPoint2D{maxRes.x / jpegFrameDim.x, maxRes.y / jpegFrameDim.y}; |
152 | | - if (MCUSize.area() != implicit_cast<uint64_t>(N_COMP)) |
153 | | - ThrowRDE("Unexpected MCU size, does not match LJpeg component count"); |
154 | | - |
155 | | - // Standard MCU layouts have MCU.x >= MCU.y: {1,1}, {2,1}, {3,1}, |
156 | | - // {4,1}, {2,2}. If the MCU is purely vertical (e.g., {1,2}), the |
157 | | - // encoder uses horizontal component interleaving with wider effective |
158 | | - // JPEG rows that must be reshaped. Fall through to inverted reshape. |
159 | | - if (MCUSize.x >= MCUSize.y) { |
160 | | - const iRectangle2D imgFrame = { |
161 | | - {static_cast<int>(offX), static_cast<int>(offY)}, |
162 | | - {static_cast<int>(w), static_cast<int>(h)}}; |
163 | | - const LJpegDecompressor::Frame jpegFrame = {MCUSize, jpegFrameDim}; |
164 | | - |
165 | | - int numLJpegRowsPerRestartInterval; |
166 | | - if (numMCUsPerRestartInterval == 0) { |
167 | | - numLJpegRowsPerRestartInterval = jpegFrameDim.y; |
168 | | - } else { |
169 | | - const int numMCUsPerRow = jpegFrameDim.x; |
170 | | - if (numMCUsPerRestartInterval % numMCUsPerRow != 0) |
171 | | - ThrowRDE("Restart interval is not a multiple of frame row size"); |
172 | | - numLJpegRowsPerRestartInterval = |
173 | | - numMCUsPerRestartInterval / numMCUsPerRow; |
174 | | - } |
175 | | - |
176 | | - LJpegDecompressor d(mRaw, imgFrame, jpegFrame, rec, |
177 | | - numLJpegRowsPerRestartInterval, |
178 | | - implicit_cast<int>(predictorMode), |
179 | | - input.peekRemainingBuffer().getAsArray1DRef()); |
180 | | - return d.decode(); |
181 | | - } |
182 | | - } |
183 | | - |
184 | | - // Inverted reshape case: |
185 | | - // The effective decoded width per JPEG row exceeds the tile width. |
186 | | - // This occurs when: |
187 | | - // (a) The JPEG frame is wider than the tile (DJI/Blackmagic CinemaDNG): |
188 | | - // e.g., JPEG=8000x1500 1-comp, tile=4000x3000. |
189 | | - // (b) Multi-component JPEG with vertical MCU ratio: |
190 | | - // e.g., JPEG=592x79 2-comp, tile=592x158 (effectiveWidth=1184). |
191 | | - // In both cases, components are interleaved horizontally, and each JPEG row |
192 | | - // contains 'widthPack' tile rows of pixel data concatenated. |
193 | | - const int effectiveJpegWidth = jpegFrameDim.x * N_COMP; |
194 | | - |
195 | | - if (effectiveJpegWidth % maxRes.x != 0) |
196 | | - ThrowRDE("Effective JPEG width is not a multiple of tile width"); |
197 | | - if (maxRes.y % jpegFrameDim.y != 0) |
198 | | - ThrowRDE("Tile height is not a multiple of LJpeg frame height"); |
199 | | - |
200 | | - const int widthPack = effectiveJpegWidth / maxRes.x; |
201 | | - if (widthPack * jpegFrameDim.y != maxRes.y) |
202 | | - ThrowRDE("Inverted reshape dimensions mismatch"); |
203 | | - |
204 | | - if (widthPack < 1 || widthPack > 4) |
205 | | - ThrowRDE("Unexpected row packing factor: %d", widthPack); |
206 | | - |
207 | | - // Decode into a temporary buffer at effective decoded dimensions. |
208 | | - // Components are always interleaved horizontally: MCU{N_COMP, 1}. |
209 | | - const auto MCUSize = iPoint2D{N_COMP, 1}; |
210 | | - |
211 | | - // Create a temporary raw image to decode the JPEG into. |
212 | | - // RawImage::create with dimensions already calls createData() internally. |
213 | | - RawImage tmpRaw = RawImage::create( |
214 | | - iPoint2D(effectiveJpegWidth, jpegFrameDim.y), RawImageType::UINT16, 1); |
215 | | - |
216 | | - const iRectangle2D tmpFrame = {{0, 0}, {effectiveJpegWidth, jpegFrameDim.y}}; |
217 | | - const LJpegDecompressor::Frame jpegFrame = {MCUSize, jpegFrameDim}; |
218 | | - |
219 | | - int numLJpegRowsPerRestartInterval; |
220 | | - if (numMCUsPerRestartInterval == 0) { |
221 | | - numLJpegRowsPerRestartInterval = jpegFrameDim.y; |
222 | | - } else { |
223 | | - const int numMCUsPerRow = jpegFrameDim.x; |
224 | | - if (numMCUsPerRestartInterval % numMCUsPerRow != 0) |
225 | | - ThrowRDE("Restart interval is not a multiple of frame row size"); |
226 | | - numLJpegRowsPerRestartInterval = numMCUsPerRestartInterval / numMCUsPerRow; |
227 | | - } |
228 | | - |
229 | | - LJpegDecompressor d(tmpRaw, tmpFrame, jpegFrame, rec, |
230 | | - numLJpegRowsPerRestartInterval, |
231 | | - implicit_cast<int>(predictorMode), |
232 | | - input.peekRemainingBuffer().getAsArray1DRef()); |
233 | | - const auto consumed = d.decode(); |
234 | | - |
235 | | - // Deinterleave: each JPEG row of width (widthPack * tileW) maps to |
236 | | - // widthPack consecutive tile rows of width tileW. |
237 | | - const auto tmpData = tmpRaw->getU16DataAsUncroppedArray2DRef(); |
238 | | - const auto outData = mRaw->getU16DataAsUncroppedArray2DRef(); |
239 | | - |
240 | | - const auto tileW = implicit_cast<int>(w); |
241 | | - const auto cpp = implicit_cast<int>(mRaw->getCpp()); |
242 | | - const int outRowPixels = cpp * tileW; |
243 | | - |
244 | | - for (int jpegRow = 0; jpegRow < jpegFrameDim.y; ++jpegRow) { |
245 | | - for (int pack = 0; pack < widthPack; ++pack) { |
246 | | - const int tileRow = implicit_cast<int>(offY) + jpegRow * widthPack + pack; |
247 | | - if (tileRow >= mRaw->dim.y) |
248 | | - continue; |
249 | | - const int srcCol = pack * outRowPixels; |
250 | | - const int dstCol = cpp * implicit_cast<int>(offX); |
251 | | - // Contiguous row-segment copy. Bounds guaranteed by validation: |
252 | | - // srcCol + outRowPixels <= widthPack * outRowPixels <= effectiveJpegWidth |
253 | | - std::memcpy(&outData(tileRow, dstCol), &tmpData(jpegRow, srcCol), |
254 | | - sizeof(uint16_t) * outRowPixels); |
255 | | - } |
256 | | - } |
257 | | - |
258 | | - return consumed; |
| 251 | + getMaxResolution(mRaw, maxDim, numComponents, jpegFrameDim); |
| 252 | + const ScanSettings settings{mRaw, |
| 253 | + {{static_cast<int>(offX), static_cast<int>(offY)}, |
| 254 | + {static_cast<int>(w), static_cast<int>(h)}}, |
| 255 | + jpegFrameDim, |
| 256 | + maxRes, |
| 257 | + {getNumLJpegRowsPerRestartInterval( |
| 258 | + numMCUsPerRestartInterval, jpegFrameDim), |
| 259 | + implicit_cast<int>(predictorMode)}, |
| 260 | + input.peekRemainingBuffer().getAsArray1DRef()}; |
| 261 | + |
| 262 | + if (const auto mcuSize = |
| 263 | + getStandardMCUSize(numComponents, jpegFrameDim, maxRes); |
| 264 | + mcuSize.hasPositiveArea()) |
| 265 | + return decodeStandardScan(settings, mcuSize, rec); |
| 266 | + |
| 267 | + return decodeInvertedScan(settings, numComponents, offX, offY, w, rec); |
259 | 268 | } |
260 | 269 |
|
261 | 270 | } // namespace rawspeed |
0 commit comments