1 /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * This file is part of openfx-supportext <https://github.com/devernay/openfx-supportext>,
4 * Copyright (C) 2013-2018 INRIA
5 *
6 * openfx-supportext is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * openfx-supportext is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with openfx-supportext. If not, see <http://www.gnu.org/licenses/gpl-2.0.html>
18 * ***** END LICENSE BLOCK ***** */
19
20 /*
21 * OFX mipmapping help functions
22 */
23
24 #include "ofxsMipMap.h"
25
26 namespace OFX {
27 // update the window of dst defined by dstRoI by halving the corresponding area in src.
28 // proofread and fixed by F. Devernay on 3/10/2014
29 template <typename PIX, int nComponents>
30 static void
halveWindow(const OfxRectI & dstRoI,const PIX * srcPixels,const OfxRectI & srcBounds,int srcRowBytes,PIX * dstPixels,const OfxRectI & dstBounds,int dstRowBytes)31 halveWindow(const OfxRectI & dstRoI,
32 const PIX* srcPixels,
33 const OfxRectI & srcBounds,
34 int srcRowBytes,
35 PIX* dstPixels,
36 const OfxRectI & dstBounds,
37 int dstRowBytes)
38 {
39 assert(srcPixels && dstPixels);
40 if (!srcPixels || !dstPixels) {
41 throwSuiteStatusException(kOfxStatFailed);
42 }
43
44 assert(dstRoI.x1 * 2 >= (srcBounds.x1 - 1) && (dstRoI.x2 - 1) * 2 < srcBounds.x2 &&
45 dstRoI.y1 * 2 >= (srcBounds.y1 - 1) && (dstRoI.y2 - 1) * 2 < srcBounds.y2);
46 int srcRowSize = srcRowBytes / sizeof(PIX);
47 int dstRowSize = dstRowBytes / sizeof(PIX);
48
49 // offset pointers so that srcData and dstData correspond to pixel (0,0)
50 const PIX* const srcData = srcPixels - (srcBounds.x1 * nComponents + srcRowSize * srcBounds.y1);
51 PIX* const dstData = dstPixels - (dstBounds.x1 * nComponents + dstRowSize * dstBounds.y1);
52
53 for (int y = dstRoI.y1; y < dstRoI.y2; ++y) {
54 const PIX* const srcLineStart = srcData + y * 2 * srcRowSize;
55 PIX* const dstLineStart = dstData + y * dstRowSize;
56
57 // The current dst row, at y, covers the src rows y*2 (thisRow) and y*2+1 (nextRow).
58 // Check that if are within srcBounds.
59 int srcy = y * 2;
60 bool pickThisRow = srcBounds.y1 <= (srcy + 0) && (srcy + 0) < srcBounds.y2;
61 bool pickNextRow = srcBounds.y1 <= (srcy + 1) && (srcy + 1) < srcBounds.y2;
62 const int sumH = (int)pickNextRow + (int)pickThisRow;
63 assert(sumH == 1 || sumH == 2);
64
65 for (int x = dstRoI.x1; x < dstRoI.x2; ++x) {
66 const PIX* const srcPixStart = srcLineStart + x * 2 * nComponents;
67 PIX* const dstPixStart = dstLineStart + x * nComponents;
68
69 // The current dst col, at y, covers the src cols x*2 (thisCol) and x*2+1 (nextCol).
70 // Check that if are within srcBounds.
71 int srcx = x * 2;
72 bool pickThisCol = srcBounds.x1 <= (srcx + 0) && (srcx + 0) < srcBounds.x2;
73 bool pickNextCol = srcBounds.x1 <= (srcx + 1) && (srcx + 1) < srcBounds.x2;
74 const int sumW = (int)pickThisCol + (int)pickNextCol;
75 assert(sumW == 1 || sumW == 2);
76 const int sum = sumW * sumH;
77 assert(0 < sum && sum <= 4);
78
79 for (int k = 0; k < nComponents; ++k) {
80 ///a b
81 ///c d
82
83 const PIX a = (pickThisCol && pickThisRow) ? *(srcPixStart + k) : 0;
84 const PIX b = (pickNextCol && pickThisRow) ? *(srcPixStart + k + nComponents) : 0;
85 const PIX c = (pickThisCol && pickNextRow) ? *(srcPixStart + k + srcRowSize) : 0;
86 const PIX d = (pickNextCol && pickNextRow) ? *(srcPixStart + k + srcRowSize + nComponents) : 0;
87
88 assert( sumW == 2 || ( sumW == 1 && ( (a == 0 && c == 0) || (b == 0 && d == 0) ) ) );
89 assert( sumH == 2 || ( sumH == 1 && ( (a == 0 && b == 0) || (c == 0 && d == 0) ) ) );
90 dstPixStart[k] = (a + b + c + d) / sum;
91 }
92 }
93 }
94 } // halveWindow
95
96 // update the window of dst defined by originalRenderWindow by mipmapping the windows of src defined by renderWindowFullRes
97 // proofread and fixed by F. Devernay on 3/10/2014
98 template <typename PIX, int nComponents>
99 static void
buildMipMapLevel(ImageEffect * instance,const OfxRectI & originalRenderWindow,const OfxRectI & renderWindowFullRes,unsigned int level,const PIX * srcPixels,const OfxRectI & srcBounds,int srcRowBytes,PIX * dstPixels,const OfxRectI & dstBounds,int dstRowBytes)100 buildMipMapLevel(ImageEffect* instance,
101 const OfxRectI & originalRenderWindow,
102 const OfxRectI & renderWindowFullRes,
103 unsigned int level,
104 const PIX* srcPixels,
105 const OfxRectI & srcBounds,
106 int srcRowBytes,
107 PIX* dstPixels,
108 const OfxRectI & dstBounds,
109 int dstRowBytes)
110 {
111 assert(level > 0);
112 assert(srcPixels && dstPixels);
113 if (!srcPixels || !dstPixels) {
114 throwSuiteStatusException(kOfxStatFailed);
115 }
116
117 auto_ptr<ImageMemory> mem;
118 size_t memSize = 0;
119 auto_ptr<ImageMemory> tmpMem;
120 size_t tmpMemSize = 0;
121 PIX* nextImg = NULL;
122 const PIX* previousImg = srcPixels;
123 OfxRectI previousBounds = srcBounds;
124 int previousRowBytes = srcRowBytes;
125 OfxRectI nextRenderWindow = renderWindowFullRes;
126
127 ///Build all the mipmap levels until we reach the one we are interested in
128 for (unsigned int i = 1; i < level; ++i) {
129 // loop invariant:
130 // - previousImg, previousBounds, previousRowBytes describe the data ate the level before i
131 // - nextRenderWindow contains the renderWindow at the level before i
132 //
133 ///Halve the smallest enclosing po2 rect as we need to render a minimum of the renderWindow
134 nextRenderWindow = downscalePowerOfTwoSmallestEnclosing(nextRenderWindow, 1);
135 # ifdef DEBUG
136 {
137 // check that doing i times 1 level is the same as doing i levels
138 OfxRectI nrw = downscalePowerOfTwoSmallestEnclosing(renderWindowFullRes, i);
139 assert(nrw.x1 == nextRenderWindow.x1 && nrw.x2 == nextRenderWindow.x2 && nrw.y1 == nextRenderWindow.y1 && nrw.y2 == nextRenderWindow.y2);
140 }
141 # endif
142 ///Allocate a temporary image if necessary, or reuse the previously allocated buffer
143 int nextRowBytes = (nextRenderWindow.x2 - nextRenderWindow.x1) * nComponents * sizeof(PIX);
144 size_t newMemSize = (nextRenderWindow.y2 - nextRenderWindow.y1) * nextRowBytes;
145 if ( tmpMem.get() ) {
146 // there should be enough memory: no need to reallocate
147 assert(tmpMemSize >= memSize);
148 } else {
149 tmpMem.reset( new ImageMemory(newMemSize, instance) );
150 tmpMemSize = newMemSize;
151 }
152 nextImg = (float*)tmpMem->lock();
153
154 halveWindow<PIX, nComponents>(nextRenderWindow, previousImg, previousBounds, previousRowBytes, nextImg, nextRenderWindow, nextRowBytes);
155
156 ///Switch for next pass
157 previousBounds = nextRenderWindow;
158 previousRowBytes = nextRowBytes;
159 previousImg = nextImg;
160 mem = tmpMem;
161 memSize = tmpMemSize;
162 }
163 // here:
164 // - previousImg, previousBounds, previousRowBytes describe the data ate the level before 'level'
165 // - nextRenderWindow contains the renderWindow at the level before 'level'
166
167 ///On the last iteration halve directly into the dstPixels
168 ///The nextRenderWindow should be equal to the original render window.
169 nextRenderWindow = downscalePowerOfTwoSmallestEnclosing(nextRenderWindow, 1);
170 assert(originalRenderWindow.x1 == nextRenderWindow.x1 && originalRenderWindow.x2 == nextRenderWindow.x2 &&
171 originalRenderWindow.y1 == nextRenderWindow.y1 && originalRenderWindow.y2 == nextRenderWindow.y2);
172
173 halveWindow<PIX, nComponents>(nextRenderWindow, previousImg, previousBounds, previousRowBytes, dstPixels, dstBounds, dstRowBytes);
174 // mem and tmpMem are freed at destruction
175 } // buildMipMapLevel
176
177 void
ofxsScalePixelData(ImageEffect * instance,const OfxRectI & originalRenderWindow,const OfxRectI & renderWindow,unsigned int levels,const void * srcPixelData,PixelComponentEnum srcPixelComponents,BitDepthEnum srcPixelDepth,const OfxRectI & srcBounds,int srcRowBytes,void * dstPixelData,PixelComponentEnum dstPixelComponents,BitDepthEnum dstPixelDepth,const OfxRectI & dstBounds,int dstRowBytes)178 ofxsScalePixelData(ImageEffect* instance,
179 const OfxRectI & originalRenderWindow,
180 const OfxRectI & renderWindow,
181 unsigned int levels,
182 const void* srcPixelData,
183 PixelComponentEnum srcPixelComponents,
184 BitDepthEnum srcPixelDepth,
185 const OfxRectI & srcBounds,
186 int srcRowBytes,
187 void* dstPixelData,
188 PixelComponentEnum dstPixelComponents,
189 BitDepthEnum dstPixelDepth,
190 const OfxRectI & dstBounds,
191 int dstRowBytes)
192 {
193 assert(srcPixelData && dstPixelData);
194 if (!srcPixelData || !dstPixelData) {
195 throwSuiteStatusException(kOfxStatFailed);
196 }
197
198 // do the rendering
199 if ( ( dstPixelDepth != eBitDepthFloat) ||
200 ( ( dstPixelComponents != ePixelComponentRGBA) &&
201 ( dstPixelComponents != ePixelComponentRGB) &&
202 ( dstPixelComponents != ePixelComponentAlpha) ) ||
203 ( dstPixelDepth != srcPixelDepth) ||
204 ( dstPixelComponents != srcPixelComponents) ) {
205 throwSuiteStatusException(kOfxStatErrFormat);
206 }
207
208 if (dstPixelComponents == ePixelComponentRGBA) {
209 buildMipMapLevel<float, 4>(instance, originalRenderWindow, renderWindow, levels, (const float*)srcPixelData,
210 srcBounds, srcRowBytes, (float*)dstPixelData, dstBounds, dstRowBytes);
211 } else if (dstPixelComponents == ePixelComponentRGB) {
212 buildMipMapLevel<float, 3>(instance, originalRenderWindow, renderWindow, levels, (const float*)srcPixelData,
213 srcBounds, srcRowBytes, (float*)dstPixelData, dstBounds, dstRowBytes);
214 } else if (dstPixelComponents == ePixelComponentAlpha) {
215 buildMipMapLevel<float, 1>(instance, originalRenderWindow, renderWindow, levels, (const float*)srcPixelData,
216 srcBounds, srcRowBytes, (float*)dstPixelData, dstBounds, dstRowBytes);
217 } // switch
218 }
219
220 template <typename PIX, int nComponents>
221 static void
ofxsBuildMipMapsForComponents(ImageEffect * instance,const OfxRectI & renderWindow,const PIX * srcPixelData,const OfxRectI & srcBounds,int srcRowBytes,unsigned int maxLevel,MipMapsVector & mipmaps)222 ofxsBuildMipMapsForComponents(ImageEffect* instance,
223 const OfxRectI & renderWindow,
224 const PIX* srcPixelData,
225 const OfxRectI & srcBounds,
226 int srcRowBytes,
227 unsigned int maxLevel,
228 MipMapsVector & mipmaps)
229 {
230 assert(srcPixelData);
231 if (!srcPixelData) {
232 throwSuiteStatusException(kOfxStatFailed);
233 }
234 const PIX* previousImg = srcPixelData;
235 OfxRectI previousBounds = srcBounds;
236 int previousRowBytes = srcRowBytes;
237 OfxRectI nextRenderWindow = renderWindow;
238
239 ///Build all the mipmap levels until we reach the one we are interested in
240 for (unsigned int i = 1; i <= maxLevel; ++i) {
241 // loop invariant:
242 // - previousImg, previousBounds, previousRowBytes describe the data ate the level before i
243 // - nextRenderWindow contains the renderWindow at the level before i
244 //
245 ///Halve the smallest enclosing po2 rect as we need to render a minimum of the renderWindow
246 nextRenderWindow = downscalePowerOfTwoSmallestEnclosing(nextRenderWindow, 1);
247 # ifdef DEBUG
248 {
249 // check that doing i times 1 level is the same as doing i levels
250 OfxRectI nrw = downscalePowerOfTwoSmallestEnclosing(renderWindowFullRes, i);
251 assert(nrw.x1 == nextRenderWindow.x1 && nrw.x2 == nextRenderWindow.x2 && nrw.y1 == nextRenderWindow.y1 && nrw.y2 == nextRenderWindow.y2);
252 }
253 # endif
254 assert(i - 1 >= 0);
255
256 ///Allocate a temporary image if necessary, or reuse the previously allocated buffer
257 int nextRowBytes = (nextRenderWindow.x2 - nextRenderWindow.x1) * nComponents * sizeof(PIX);
258 mipmaps[i - 1].memSize = (nextRenderWindow.y2 - nextRenderWindow.y1) * nextRowBytes;
259 mipmaps[i - 1].bounds = nextRenderWindow;
260
261 mipmaps[i - 1].data = new ImageMemory(mipmaps[i - 1].memSize, instance);
262 tmpMemSize = newMemSize;
263
264 float* nextImg = (float*)tmpMem->lock();
265
266 halveWindow<PIX, nComponents>(nextRenderWindow, previousImg, previousBounds, previousRowBytes, nextImg, nextRenderWindow, nextRowBytes);
267
268 ///Switch for next pass
269 previousBounds = nextRenderWindow;
270 previousRowBytes = nextRowBytes;
271 previousImg = nextImg;
272 }
273 }
274
275 void
ofxsBuildMipMaps(ImageEffect * instance,const OfxRectI & renderWindow,const void * srcPixelData,PixelComponentEnum srcPixelComponents,BitDepthEnum srcPixelDepth,const OfxRectI & srcBounds,int srcRowBytes,unsigned int maxLevel,MipMapsVector & mipmaps)276 ofxsBuildMipMaps(ImageEffect* instance,
277 const OfxRectI & renderWindow,
278 const void* srcPixelData,
279 PixelComponentEnum srcPixelComponents,
280 BitDepthEnum srcPixelDepth,
281 const OfxRectI & srcBounds,
282 int srcRowBytes,
283 unsigned int maxLevel,
284 MipMapsVector & mipmaps)
285 {
286 assert(srcPixelData && mipmaps->size() == maxLevel);
287 if ( !srcPixelData || (mipmaps->size() != maxLevel) ) {
288 throwSuiteStatusException(kOfxStatFailed);
289 }
290
291 // do the rendering
292 if ( srcPixelData && ( ( srcPixelDepth != eBitDepthFloat) ||
293 ( ( srcPixelComponents != ePixelComponentRGBA) &&
294 ( srcPixelComponents != ePixelComponentRGB) &&
295 ( srcPixelComponents != ePixelComponentAlpha) ) ) ) {
296 throwSuiteStatusException(kOfxStatErrFormat);
297 }
298
299 if (dstPixelComponents == ePixelComponentRGBA) {
300 ofxsBuildMipMapsForComponents<float, 4>(instance, renderWindow, srcPixelData, srcBounds,
301 srcRowBytes, maxLevel, mipmaps);
302 } else if (dstPixelComponents == ePixelComponentRGB) {
303 ofxsBuildMipMapsForComponents<float, 3>(instance, renderWindow, srcPixelData, srcBounds,
304 srcRowBytes, maxLevel, mipmaps);
305 } else if (dstPixelComponents == ePixelComponentAlpha) {
306 ofxsBuildMipMapsForComponents<float, 1>(instance, renderWindow, srcPixelData, srcBounds,
307 srcRowBytes, maxLevel, mipmaps);
308 }
309 }
310 } // OFX
311