@@ -17,21 +17,24 @@ export interface PerfectPixelOptions {
1717}
1818
1919const FFT_THUMB_MAX = 512
20+ const TRANSPARENT_ALPHA = 8
2021
21- function rgbaToRgbFloat ( imageData : ImageData ) : { r : Float32Array ; g : Float32Array ; b : Float32Array ; w : number ; h : number } {
22+ function rgbaToRgbaFloat ( imageData : ImageData ) : { r : Float32Array ; g : Float32Array ; b : Float32Array ; a : Float32Array ; w : number ; h : number } {
2223 const w = imageData . width
2324 const h = imageData . height
2425 const d = imageData . data
2526 const n = w * h
2627 const r = new Float32Array ( n )
2728 const g = new Float32Array ( n )
2829 const b = new Float32Array ( n )
30+ const a = new Float32Array ( n )
2931 for ( let i = 0 , p = 0 ; i < n ; i ++ , p += 4 ) {
3032 r [ i ] = d [ p ] !
3133 g [ i ] = d [ p + 1 ] !
3234 b [ i ] = d [ p + 2 ] !
35+ a [ i ] = d [ p + 3 ] !
3336 }
34- return { r, g, b, w, h }
37+ return { r, g, b, a , w, h }
3538}
3639
3740function rgbToGrayFloat32 ( r : Float32Array , g : Float32Array , b : Float32Array , n : number ) : Float32Array {
@@ -419,6 +422,7 @@ function sampleCenter(
419422 r : Float32Array ,
420423 g : Float32Array ,
421424 b : Float32Array ,
425+ a : Float32Array ,
422426 W : number ,
423427 H : number ,
424428 xCoords : number [ ] ,
@@ -439,10 +443,18 @@ function sampleCenter(
439443 Math . max ( 0 , Math . round ( ( xCoords [ i ] ! + xCoords [ i + 1 ] ! ) * 0.5 ) ) ,
440444 )
441445 const si = cy * W + cx
442- data [ o ++ ] = r [ si ] !
443- data [ o ++ ] = g [ si ] !
444- data [ o ++ ] = b [ si ] !
445- data [ o ++ ] = 255
446+ const aa = a [ si ] !
447+ if ( aa <= TRANSPARENT_ALPHA ) {
448+ data [ o ++ ] = 0
449+ data [ o ++ ] = 0
450+ data [ o ++ ] = 0
451+ data [ o ++ ] = 0
452+ } else {
453+ data [ o ++ ] = r [ si ] !
454+ data [ o ++ ] = g [ si ] !
455+ data [ o ++ ] = b [ si ] !
456+ data [ o ++ ] = aa
457+ }
446458 }
447459 }
448460 return new ImageData ( data , nx , ny )
@@ -452,6 +464,7 @@ function sampleMedian(
452464 r : Float32Array ,
453465 g : Float32Array ,
454466 b : Float32Array ,
467+ a : Float32Array ,
455468 W : number ,
456469 H : number ,
457470 xCoords : number [ ] ,
@@ -460,7 +473,10 @@ function sampleMedian(
460473 const nx = xCoords . length - 1
461474 const ny = yCoords . length - 1
462475 const data = new Uint8ClampedArray ( nx * ny * 4 )
463- const buf : number [ ] = [ ]
476+ const rs : number [ ] = [ ]
477+ const gs : number [ ] = [ ]
478+ const bs : number [ ] = [ ]
479+ const alphas : number [ ] = [ ]
464480 let o = 0
465481 for ( let j = 0 ; j < ny ; j ++ ) {
466482 const y0 = Math . max ( 0 , Math . min ( H , Math . floor ( yCoords [ j ] ! ) ) )
@@ -470,36 +486,47 @@ function sampleMedian(
470486 const x0 = Math . max ( 0 , Math . min ( W , Math . floor ( xCoords [ i ] ! ) ) )
471487 let x1 = Math . max ( 0 , Math . min ( W , Math . floor ( xCoords [ i + 1 ] ! ) ) )
472488 if ( x1 <= x0 ) x1 = Math . min ( x0 + 1 , W )
473- buf . length = 0
489+ rs . length = 0
490+ gs . length = 0
491+ bs . length = 0
492+ alphas . length = 0
474493 for ( let y = y0 ; y < y1 ; y ++ ) {
475494 for ( let x = x0 ; x < x1 ; x ++ ) {
476495 const si = y * W + x
477- buf . push ( r [ si ] ! , g [ si ] ! , b [ si ] ! )
496+ const aa = a [ si ] !
497+ alphas . push ( aa )
498+ if ( aa > TRANSPARENT_ALPHA ) {
499+ rs . push ( r [ si ] ! )
500+ gs . push ( g [ si ] ! )
501+ bs . push ( b [ si ] ! )
502+ }
478503 }
479504 }
480- if ( buf . length === 0 ) {
505+ if ( alphas . length === 0 || rs . length === 0 ) {
481506 data [ o ] = 0
482507 data [ o + 1 ] = 0
483508 data [ o + 2 ] = 0
484- data [ o + 3 ] = 255
509+ data [ o + 3 ] = 0
485510 o += 4
486511 continue
487512 }
488- const rs : number [ ] = [ ]
489- const gs : number [ ] = [ ]
490- const bs : number [ ] = [ ]
491- for ( let k = 0 ; k < buf . length ; k += 3 ) {
492- rs . push ( buf [ k ] ! )
493- gs . push ( buf [ k + 1 ] ! )
494- bs . push ( buf [ k + 2 ] ! )
513+ alphas . sort ( ( x , y ) => x - y )
514+ const alpha = Math . round ( medianSorted ( alphas ) )
515+ if ( alpha <= TRANSPARENT_ALPHA ) {
516+ data [ o ] = 0
517+ data [ o + 1 ] = 0
518+ data [ o + 2 ] = 0
519+ data [ o + 3 ] = 0
520+ o += 4
521+ continue
495522 }
496- rs . sort ( ( a , b ) => a - b )
497- gs . sort ( ( a , b ) => a - b )
498- bs . sort ( ( a , b ) => a - b )
523+ rs . sort ( ( x , y ) => x - y )
524+ gs . sort ( ( x , y ) => x - y )
525+ bs . sort ( ( x , y ) => x - y )
499526 data [ o ++ ] = Math . round ( medianSorted ( rs ) )
500527 data [ o ++ ] = Math . round ( medianSorted ( gs ) )
501528 data [ o ++ ] = Math . round ( medianSorted ( bs ) )
502- data [ o ++ ] = 255
529+ data [ o ++ ] = alpha
503530 }
504531 }
505532 return new ImageData ( data , nx , ny )
@@ -587,6 +614,7 @@ function sampleMajority(
587614 r : Float32Array ,
588615 g : Float32Array ,
589616 b : Float32Array ,
617+ a : Float32Array ,
590618 W : number ,
591619 H : number ,
592620 xCoords : number [ ] ,
@@ -597,7 +625,8 @@ function sampleMajority(
597625 const ny = yCoords . length - 1
598626 const data = new Uint8ClampedArray ( nx * ny * 4 )
599627 const cellBuf = new Float32Array ( maxSamples * 3 )
600- const pixelList : number [ ] = [ ]
628+ const visiblePixels : number [ ] = [ ]
629+ const alphas : number [ ] = [ ]
601630 let o = 0
602631
603632 for ( let j = 0 ; j < ny ; j ++ ) {
@@ -608,43 +637,52 @@ function sampleMajority(
608637 const x0 = Math . max ( 0 , Math . min ( W , Math . floor ( xCoords [ i ] ! ) ) )
609638 let x1 = Math . max ( 0 , Math . min ( W , Math . floor ( xCoords [ i + 1 ] ! ) ) )
610639 if ( x1 <= x0 ) x1 = Math . min ( x0 + 1 , W )
611- pixelList . length = 0
640+ visiblePixels . length = 0
641+ alphas . length = 0
642+ let totalPixels = 0
612643 for ( let y = y0 ; y < y1 ; y ++ ) {
613644 for ( let x = x0 ; x < x1 ; x ++ ) {
614- pixelList . push ( y * W + x )
645+ totalPixels ++
646+ const si = y * W + x
647+ const aa = a [ si ] !
648+ if ( aa > TRANSPARENT_ALPHA ) {
649+ visiblePixels . push ( si )
650+ alphas . push ( aa )
651+ }
615652 }
616653 }
617- const np = pixelList . length
618- if ( np === 0 ) {
654+ const visibleN = visiblePixels . length
655+ if ( totalPixels === 0 || visibleN === 0 || visibleN * 2 < totalPixels ) {
619656 data [ o ] = 0
620657 data [ o + 1 ] = 0
621658 data [ o + 2 ] = 0
622- data [ o + 3 ] = 255
659+ data [ o + 3 ] = 0
623660 o += 4
624661 continue
625662 }
626- let useN = np
627- if ( np > maxSamples ) {
663+ let useN = visibleN
664+ if ( visibleN > maxSamples ) {
628665 useN = maxSamples
629666 for ( let s = 0 ; s < useN ; s ++ ) {
630- const pick = pixelList [ Math . floor ( Math . random ( ) * np ) ] !
667+ const pick = visiblePixels [ Math . floor ( Math . random ( ) * visibleN ) ] !
631668 cellBuf [ s * 3 ] = r [ pick ] !
632669 cellBuf [ s * 3 + 1 ] = g [ pick ] !
633670 cellBuf [ s * 3 + 2 ] = b [ pick ] !
634671 }
635672 } else {
636- for ( let s = 0 ; s < np ; s ++ ) {
637- const pick = pixelList [ s ] !
673+ for ( let s = 0 ; s < visibleN ; s ++ ) {
674+ const pick = visiblePixels [ s ] !
638675 cellBuf [ s * 3 ] = r [ pick ] !
639676 cellBuf [ s * 3 + 1 ] = g [ pick ] !
640677 cellBuf [ s * 3 + 2 ] = b [ pick ] !
641678 }
642679 }
643680 const [ rr , gg , bb ] = kmeans2RgbCell ( cellBuf . subarray ( 0 , useN * 3 ) , useN )
681+ alphas . sort ( ( x , y ) => x - y )
644682 data [ o ++ ] = rr
645683 data [ o ++ ] = gg
646684 data [ o ++ ] = bb
647- data [ o ++ ] = 255
685+ data [ o ++ ] = Math . round ( medianSorted ( alphas ) )
648686 }
649687 }
650688 return new ImageData ( data , nx , ny )
@@ -749,7 +787,7 @@ export function getPerfectPixel(imageData: ImageData, opts: PerfectPixelOptions
749787 const refineIntensity = opts . refineIntensity ?? 0.25
750788 const fixSquare = opts . fixSquare ?? true
751789
752- const { r, g, b, w, h } = rgbaToRgbFloat ( imageData )
790+ const { r, g, b, a , w, h } = rgbaToRgbaFloat ( imageData )
753791 const gray = rgbToGrayFloat32 ( r , g , b , w * h )
754792
755793 let sizeX : number
@@ -770,11 +808,11 @@ export function getPerfectPixel(imageData: ImageData, opts: PerfectPixelOptions
770808
771809 let out : ImageData
772810 if ( sampleMethod === 'majority' ) {
773- out = sampleMajority ( r , g , b , W , H , xCoords , yCoords )
811+ out = sampleMajority ( r , g , b , a , W , H , xCoords , yCoords )
774812 } else if ( sampleMethod === 'median' ) {
775- out = sampleMedian ( r , g , b , W , H , xCoords , yCoords )
813+ out = sampleMedian ( r , g , b , a , W , H , xCoords , yCoords )
776814 } else {
777- out = sampleCenter ( r , g , b , W , H , xCoords , yCoords )
815+ out = sampleCenter ( r , g , b , a , W , H , xCoords , yCoords )
778816 }
779817
780818 return fixSquareOutput ( out , fixSquare )
0 commit comments