@@ -12,14 +12,17 @@ export const encodeSRGB = (v: number): number => 255 * (v <= 0.0031308 ? 12.92 *
1212// the luma weights (BT.709, linear) a single channel map's ratio uses
1313const LUMA = [ 0.2126 , 0.7152 , 0.0722 ] ;
1414
15+ // per channel: the log2 gain of a stored value
16+ const gainTables = ( map : GainMap ) : Float32Array [ ] => [ 0 , 1 , 2 ] . map ( c => {
17+ const min = map . gainMapMin [ c ] , max = map . gainMapMax [ c ] , gamma = map . gamma [ c ] ;
18+ return new Float32Array ( 256 ) . map ( ( _ , v ) => min + ( max - min ) * ( v / 255 ) ** ( 1 / gamma ) ) ;
19+ } ) ;
20+
1521/** The map's log2 gains, 3 per pixel */
1622export function decodeGains ( map : GainMap ) : Float32Array {
1723 const { bitmap } = map . image ;
1824 const gains = new Float32Array ( bitmap . length / 4 * 3 ) ;
19- const tables = [ 0 , 1 , 2 ] . map ( c => {
20- const min = map . gainMapMin [ c ] , max = map . gainMapMax [ c ] , gamma = map . gamma [ c ] ;
21- return new Float32Array ( 256 ) . map ( ( _ , v ) => min + ( max - min ) * ( v / 255 ) ** ( 1 / gamma ) ) ;
22- } ) ;
25+ const tables = gainTables ( map ) ;
2326 for ( let i = 0 , g = 0 ; i < bitmap . length ; i += 4 , g += 3 ) {
2427 gains [ g ] = tables [ 0 ] [ bitmap [ i ] ] ;
2528 gains [ g + 1 ] = tables [ 1 ] [ bitmap [ i + 1 ] ] ;
@@ -101,18 +104,24 @@ export function redraw(image: Image, map: GainMap, left: number, top: number, ri
101104 if ( right <= left || bottom <= top ) return ;
102105 const mapWidth = map . width , mapHeight = map . height ;
103106 const sx = mapWidth / width , sy = mapHeight / height ;
104- const gains = decodeGains ( map ) ;
105107 const oS = map . offsetSDR , oH = map . offsetHDR ;
106108 const lo = Math . min ( 0 , ...map . gainMapMin ) , hi = Math . max ( 0 , ...map . gainMapMax , extraMax ) ;
107109 const S = [ 0 , 0 , 0 ] , H = [ 0 , 0 , 0 ] , sumS = [ 0 , 0 , 0 ] , sumH = [ 0 , 0 , 0 ] ;
108110 const i0 = Math . max ( 0 , Math . floor ( left * sx ) ) , i1 = Math . min ( mapWidth , Math . ceil ( right * sx ) ) ;
109111 const j0 = Math . max ( 0 , Math . floor ( top * sy ) ) , j1 = Math . min ( mapHeight , Math . ceil ( bottom * sy ) ) ;
112+ if ( i1 <= i0 || j1 <= j0 ) return ;
113+
114+ // only the map pixels the drawing covers are read and recomputed (a small box on a large image shouldn't cost the
115+ // whole map)
116+ const tables = gainTables ( map ) , stored = map . image . bitmap ;
117+ const regionWidth = i1 - i0 ;
118+ const next = new Float32Array ( regionWidth * ( j1 - j0 ) * 3 ) ;
110119 for ( let j = j0 ; j < j1 ; j ++ ) {
111120 const y0 = Math . max ( 0 , Math . floor ( j / sy ) ) , y1 = Math . min ( height , Math . max ( y0 + 1 , Math . floor ( ( j + 1 ) / sy ) ) ) ;
112121 for ( let i = i0 ; i < i1 ; i ++ ) {
113122 const x0 = Math . max ( 0 , Math . floor ( i / sx ) ) , x1 = Math . min ( width , Math . max ( x0 + 1 , Math . floor ( ( i + 1 ) / sx ) ) ) ;
114- const g = 3 * ( j * mapWidth + i ) ;
115- const boost = [ 2 ** gains [ g ] , 2 ** gains [ g + 1 ] , 2 ** gains [ g + 2 ] ] ;
123+ const m = 4 * ( j * mapWidth + i ) , g = 3 * ( ( j - j0 ) * regionWidth + ( i - i0 ) ) ;
124+ const boost = [ 2 ** tables [ 0 ] [ stored [ m ] ] , 2 ** tables [ 1 ] [ stored [ m + 1 ] ] , 2 ** tables [ 2 ] [ stored [ m + 2 ] ] ] ;
116125 sumS . fill ( 0 ) ;
117126 sumH . fill ( 0 ) ;
118127 for ( let y = y0 ; y < y1 ; y ++ )
@@ -132,10 +141,35 @@ export function redraw(image: Image, map: GainMap, left: number, top: number, ri
132141 s += LUMA [ c ] * ( sumS [ c ] / n + oS [ c ] ) ;
133142 h += LUMA [ c ] * ( sumH [ c ] / n + oH [ c ] ) ;
134143 }
135- gains [ g ] = gains [ g + 1 ] = gains [ g + 2 ] = ratio ( h , s , lo , hi ) ;
136- } else for ( let c = 0 ; c < 3 ; c ++ ) gains [ g + c ] = ratio ( sumH [ c ] / n + oH [ c ] , sumS [ c ] / n + oS [ c ] , lo , hi ) ;
144+ next [ g ] = next [ g + 1 ] = next [ g + 2 ] = ratio ( h , s , lo , hi ) ;
145+ } else for ( let c = 0 ; c < 3 ; c ++ ) next [ g + c ] = ratio ( sumH [ c ] / n + oH [ c ] , sumS [ c ] / n + oS [ c ] , lo , hi ) ;
137146 }
138147 }
148+
149+ // when the new gains are in the map's range (usual: drawing seldom brightens beyond the highlights), only those
150+ // pixels are stored and the others stay as they are
151+ const { gainMapMin : min , gainMapMax : max , gamma } = map ;
152+ let fits = ! single || ( min [ 0 ] === min [ 1 ] && min [ 1 ] === min [ 2 ] && max [ 0 ] === max [ 1 ] && max [ 1 ] === max [ 2 ] && gamma [ 0 ] === gamma [ 1 ] && gamma [ 1 ] === gamma [ 2 ] ) ;
153+ for ( let g = 0 ; fits && g < next . length ; g += 3 )
154+ for ( let c = 0 ; c < 3 ; c ++ ) if ( ! ( next [ g + c ] >= min [ c ] && next [ g + c ] <= max [ c ] ) ) fits = false ;
155+ if ( fits ) {
156+ for ( let j = j0 ; j < j1 ; j ++ )
157+ for ( let i = i0 , g = 3 * ( j - j0 ) * regionWidth , m = 4 * ( j * mapWidth + i0 ) ; i < i1 ; i ++ , g += 3 , m += 4 )
158+ for ( let c = 0 ; c < 3 ; c ++ ) {
159+ const t = max [ c ] > min [ c ] ? ( next [ g + c ] - min [ c ] ) / ( max [ c ] - min [ c ] ) : 0 ;
160+ stored [ m + c ] = 255 * Math . min ( 1 , Math . max ( 0 , t ) ) ** gamma [ c ] ;
161+ }
162+ return ;
163+ }
164+
165+ // otherwise the range grows, and every stored value is recomputed for it
166+ const gains = decodeGains ( map ) ;
167+ for ( let j = j0 ; j < j1 ; j ++ )
168+ for ( let i = i0 , g = 3 * ( j - j0 ) * regionWidth , m = 3 * ( j * mapWidth + i0 ) ; i < i1 ; i ++ , g += 3 , m += 3 ) {
169+ gains [ m ] = next [ g ] ;
170+ gains [ m + 1 ] = next [ g + 1 ] ;
171+ gains [ m + 2 ] = next [ g + 2 ] ;
172+ }
139173 encodeGains ( map , gains , mapWidth , mapHeight , single ) ;
140174}
141175
0 commit comments