@@ -29,7 +29,7 @@ const presetSettings = {
2929 clean : { brightness : 16 , contrast : 114 , grain : 1 , vignette : 2 , threshold : 188 , shadowBoost : 0.14 , binaryMix : 0.58 , lineNoise : 0.014 , tonerNoise : 0.006 } ,
3030 classic : { brightness : 8 , contrast : 132 , grain : 6 , vignette : 5 , threshold : 166 , shadowBoost : 0.28 , binaryMix : 0.74 , lineNoise : 0.028 , tonerNoise : 0.012 } ,
3131 "high-contrast" : { brightness : 3 , contrast : 152 , grain : 3 , vignette : 1 , threshold : 150 , shadowBoost : 0.4 , binaryMix : 0.88 , lineNoise : 0.018 , tonerNoise : 0.009 } ,
32- fax : { brightness : 0 , contrast : 168 , grain : 7 , vignette : 1 , threshold : 142 , shadowBoost : 0.46 , binaryMix : 0.96 , lineNoise : 0.045 , tonerNoise : 0.022 } ,
32+ fax : { brightness : 0 , contrast : 172 , grain : 7 , vignette : 1 , threshold : 142 , shadowBoost : 0.46 , binaryMix : 1 , lineNoise : 0.045 , tonerNoise : 0.022 , processingScale : 0.42 , pureMono : true , streakStrength : 0.055 , dropoutChance : 0.0024 } ,
3333} ;
3434
3535const presetDescriptions = {
@@ -1230,20 +1230,52 @@ function loadImage(file) {
12301230 } ) ;
12311231}
12321232
1233- function applyScanEffect ( sourceImage , controls , dimensions = { width : sourceImage . width , height : sourceImage . height } ) {
1234- const canvas = document . createElement ( "canvas" ) ;
1235- canvas . width = dimensions . width ;
1236- canvas . height = dimensions . height ;
1237- const context = canvas . getContext ( "2d" , { willReadFrequently : true } ) ;
1238- context . fillStyle = "#ffffff" ;
1239- context . fillRect ( 0 , 0 , canvas . width , canvas . height ) ;
1240- context . imageSmoothingEnabled = true ;
1241- context . imageSmoothingQuality = canvas . width < sourceImage . width ? "medium" : "high" ;
1242- context . drawImage ( sourceImage , 0 , 0 ) ;
1233+ function applyFaxArtifacts ( data , width , height , preset ) {
1234+ const streakStrength = preset . streakStrength ?? 0.04 ;
1235+ const dropoutChance = preset . dropoutChance ?? 0.002 ;
12431236
1244- const imageData = context . getImageData ( 0 , 0 , canvas . width , canvas . height ) ;
1245- const data = imageData . data ;
1237+ for ( let y = 0 ; y < height ; y += 1 ) {
1238+ if ( Math . random ( ) < streakStrength ) {
1239+ const segmentLength = Math . max ( 10 , Math . round ( width * ( 0.05 + ( Math . random ( ) * 0.18 ) ) ) ) ;
1240+ const startX = Math . max ( 0 , Math . floor ( Math . random ( ) * Math . max ( 1 , width - segmentLength ) ) ) ;
1241+ const streakValue = Math . random ( ) < 0.82 ? 255 : 0 ;
1242+
1243+ for ( let x = startX ; x < startX + segmentLength ; x += 1 ) {
1244+ const index = ( ( y * width ) + x ) * 4 ;
1245+ data [ index ] = streakValue ;
1246+ data [ index + 1 ] = streakValue ;
1247+ data [ index + 2 ] = streakValue ;
1248+ }
1249+ }
1250+
1251+ for ( let x = 0 ; x < width ; x += 1 ) {
1252+ const index = ( ( y * width ) + x ) * 4 ;
1253+ if ( data [ index ] === 0 && Math . random ( ) < dropoutChance ) {
1254+ data [ index ] = 255 ;
1255+ data [ index + 1 ] = 255 ;
1256+ data [ index + 2 ] = 255 ;
1257+ }
1258+ }
1259+ }
1260+ }
1261+
1262+ function applyScanEffect ( sourceImage , controls , dimensions = { width : sourceImage . width , height : sourceImage . height } ) {
12461263 const preset = presetSettings [ controls . preset ] ;
1264+ const processingScale = preset . processingScale ?? 1 ;
1265+ const processingWidth = Math . max ( 1 , Math . round ( dimensions . width * processingScale ) ) ;
1266+ const processingHeight = Math . max ( 1 , Math . round ( dimensions . height * processingScale ) ) ;
1267+ const processingCanvas = document . createElement ( "canvas" ) ;
1268+ processingCanvas . width = processingWidth ;
1269+ processingCanvas . height = processingHeight ;
1270+ const processingContext = processingCanvas . getContext ( "2d" , { willReadFrequently : true } ) ;
1271+ processingContext . fillStyle = "#ffffff" ;
1272+ processingContext . fillRect ( 0 , 0 , processingCanvas . width , processingCanvas . height ) ;
1273+ processingContext . imageSmoothingEnabled = true ;
1274+ processingContext . imageSmoothingQuality = processingCanvas . width < sourceImage . width ? "medium" : "high" ;
1275+ processingContext . drawImage ( sourceImage , 0 , 0 , processingCanvas . width , processingCanvas . height ) ;
1276+
1277+ const imageData = processingContext . getImageData ( 0 , 0 , processingCanvas . width , processingCanvas . height ) ;
1278+ const data = imageData . data ;
12471279 const contrastFactor = controls . contrast / 100 ;
12481280 const brightnessOffset = controls . brightness * 1.8 ;
12491281 const grainAmount = controls . grain ;
@@ -1252,12 +1284,12 @@ function applyScanEffect(sourceImage, controls, dimensions = { width: sourceImag
12521284 const binaryMix = preset . binaryMix ?? 0.72 ;
12531285 const lineNoise = preset . lineNoise ?? 0.02 ;
12541286 const tonerNoise = preset . tonerNoise ?? 0.01 ;
1255- const rowJitterSeed = Math . max ( 1 , Math . round ( canvas . height / 140 ) ) ;
1287+ const rowJitterSeed = Math . max ( 1 , Math . round ( processingCanvas . height / 140 ) ) ;
12561288
12571289 for ( let index = 0 ; index < data . length ; index += 4 ) {
12581290 const pixelIndex = index / 4 ;
1259- const x = pixelIndex % canvas . width ;
1260- const y = Math . floor ( pixelIndex / canvas . width ) ;
1291+ const x = pixelIndex % processingCanvas . width ;
1292+ const y = Math . floor ( pixelIndex / processingCanvas . width ) ;
12611293 const red = data [ index ] ;
12621294 const green = data [ index + 1 ] ;
12631295 const blue = data [ index + 2 ] ;
@@ -1266,7 +1298,7 @@ function applyScanEffect(sourceImage, controls, dimensions = { width: sourceImag
12661298 const noise = ( Math . random ( ) - 0.5 ) * grainAmount * 2 ;
12671299 const scanLineOffset = Math . sin ( ( y / rowJitterSeed ) * 0.9 ) * 255 * lineNoise ;
12681300 const feedOffset = ( ( y % 3 ) === 0 ? - 1 : 1 ) * 255 * lineNoise * 0.18 ;
1269- const edgeFalloff = ( ( x / Math . max ( 1 , canvas . width ) ) - 0.5 ) * 255 * lineNoise * 0.08 ;
1301+ const edgeFalloff = ( ( x / Math . max ( 1 , processingCanvas . width ) ) - 0.5 ) * 255 * lineNoise * 0.08 ;
12701302 const adjustedGray = clamp ( ( ( baseGray - 128 ) * contrastFactor ) + 128 + brightnessOffset + noise + scanLineOffset + feedOffset + edgeFalloff ) ;
12711303 const cleanupLift = controls . autoCleanup ? Math . max ( 0 , adjustedGray - 168 ) * 0.72 : 0 ;
12721304 const cleanedGray = clamp ( adjustedGray + cleanupLift ) ;
@@ -1288,6 +1320,11 @@ function applyScanEffect(sourceImage, controls, dimensions = { width: sourceImag
12881320 grayscaleValue = 255 ;
12891321 }
12901322
1323+ if ( preset . pureMono ) {
1324+ const rowThreshold = threshold + Math . sin ( y / 5.5 ) * 8 + ( ( ( y % 4 ) - 1.5 ) * 1.5 ) ;
1325+ grayscaleValue = grayscaleValue < rowThreshold ? 0 : 255 ;
1326+ }
1327+
12911328 if ( backgroundCandidate || ( controls . autoCleanup && grayscaleValue > threshold + 14 ) ) {
12921329 grayscaleValue = 255 ;
12931330 }
@@ -1297,11 +1334,28 @@ function applyScanEffect(sourceImage, controls, dimensions = { width: sourceImag
12971334 data [ index + 2 ] = grayscaleValue ;
12981335 }
12991336
1300- context . putImageData ( imageData , 0 , 0 ) ;
1301- applyPaperWash ( context , canvas . width , canvas . height ) ;
1302- applyVignette ( context , canvas . width , canvas . height , controls . vignette ) ;
1303- applyShadowEdge ( context , canvas . width , canvas . height ) ;
1304- return canvas ;
1337+ if ( preset . pureMono ) {
1338+ applyFaxArtifacts ( data , processingCanvas . width , processingCanvas . height , preset ) ;
1339+ }
1340+
1341+ processingContext . putImageData ( imageData , 0 , 0 ) ;
1342+
1343+ const outputCanvas = document . createElement ( "canvas" ) ;
1344+ outputCanvas . width = dimensions . width ;
1345+ outputCanvas . height = dimensions . height ;
1346+ const outputContext = outputCanvas . getContext ( "2d" , { willReadFrequently : true } ) ;
1347+ outputContext . fillStyle = "#ffffff" ;
1348+ outputContext . fillRect ( 0 , 0 , outputCanvas . width , outputCanvas . height ) ;
1349+ outputContext . imageSmoothingEnabled = ! preset . pureMono ;
1350+ outputContext . imageSmoothingQuality = preset . pureMono ? "low" : "high" ;
1351+ outputContext . drawImage ( processingCanvas , 0 , 0 , outputCanvas . width , outputCanvas . height ) ;
1352+
1353+ if ( ! preset . pureMono ) {
1354+ applyPaperWash ( outputContext , outputCanvas . width , outputCanvas . height ) ;
1355+ applyShadowEdge ( outputContext , outputCanvas . width , outputCanvas . height ) ;
1356+ }
1357+ applyVignette ( outputContext , outputCanvas . width , outputCanvas . height , controls . vignette ) ;
1358+ return outputCanvas ;
13051359}
13061360
13071361function applyPaperWash ( context , width , height ) {
0 commit comments