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