@@ -563,74 +563,137 @@ public Bitmap getOpaqueImage() throws IOException
563
563
return SampledImageReader .getRGBImage (this , null );
564
564
}
565
565
566
- // explicit mask: RGB + Binary -> ARGB
567
- // soft mask: RGB + Gray -> ARGB
568
- private Bitmap applyMask (Bitmap image , Bitmap mask ,
569
- boolean isSoft , float [] matte )
566
+ /**
567
+ * @param image The image to apply the mask to as alpha channel.
568
+ * @param mask A mask image in 8 bit Gray. Even for a stencil mask image due to
569
+ * {@link #getOpaqueImage()} and {@link SampledImageReader}'s {@code from1Bit()} special
570
+ * handling of DeviceGray.
571
+ * @param isSoft {@code true} if a soft mask. If not stencil mask, then alpha will be inverted
572
+ * by this method.
573
+ * @param matte an optional RGB matte if a soft mask.
574
+ * @return an ARGB image (can be the altered original image)
575
+ */
576
+ private Bitmap applyMask (Bitmap image , Bitmap mask , boolean isSoft , float [] matte )
570
577
{
571
578
if (mask == null )
572
579
{
573
580
return image ;
574
581
}
575
582
576
- int width = image .getWidth ();
577
- int height = image .getHeight ();
583
+ final int width = Math . max ( image .getWidth (), mask . getWidth () );
584
+ final int height = Math . max ( image .getHeight (), mask . getHeight () );
578
585
579
- // scale mask to fit image, or image to fit mask, whichever is larger
586
+ // scale mask to fit image, or image to fit mask, whichever is larger.
587
+ // also make sure that mask is 8 bit gray and image is ARGB as this
588
+ // is what needs to be returned.
580
589
if (mask .getWidth () < width || mask .getHeight () < height )
581
590
{
582
591
mask = scaleImage (mask , width , height );
583
592
}
584
-
585
- if (mask .getWidth () > width || mask .getHeight () > height )
593
+ if (image .getWidth () < width || image .getHeight () < height )
586
594
{
587
- width = mask .getWidth ();
588
- height = mask .getHeight ();
589
595
image = scaleImage (image , width , height );
590
596
}
597
+ if (image .getConfig () != Bitmap .Config .ARGB_8888 || !image .isMutable ())
598
+ {
599
+ image = image .copy (Bitmap .Config .ARGB_8888 , true );
600
+ }
601
+ int [] pixels = new int [width ];
602
+ int [] maskPixels = new int [width ];
591
603
592
- // compose to ARGB
593
- Bitmap masked = Bitmap .createBitmap (width , height , Bitmap .Config .ARGB_8888 );
594
- int [] destRow = new int [width ];
595
-
596
- int r , g , b , alpha ;
597
- int [] alphaRow = new int [width ];
598
- int [] rgbaRow = new int [width ];
599
- for (int y = 0 ; y < height ; y ++)
604
+ // compose alpha into ARGB image, either:
605
+ // - very fast by direct bit combination if not a soft mask and a 8 bit alpha source.
606
+ // - fast by letting the sample model do a bulk band operation if no matte is set.
607
+ // - slow and complex by matte calculations on individual pixel components.
608
+ if (!isSoft && image .getByteCount () == mask .getByteCount ())
600
609
{
601
- image .getPixels (rgbaRow , 0 , width , 0 , y , width , 1 );
602
- mask .getPixels (alphaRow , 0 , width , 0 , y , width , 1 );
603
- for (int x = 0 ; x < width ; x ++)
610
+ for (int y = 0 ; y < height ; y ++)
604
611
{
605
- r = Color .red (rgbaRow [x ]);
606
- g = Color .green (rgbaRow [x ]);
607
- b = Color .blue (rgbaRow [x ]);
608
- if (isSoft )
612
+ image .getPixels (pixels , 0 , width , 0 , y , width , 1 );
613
+ mask .getPixels (maskPixels , 0 , width , 0 , y , width , 1 );
614
+ for (int i = 0 , c = width ; c > 0 ; i ++, c --)
609
615
{
610
- alpha = Color .alpha (alphaRow [x ]);
611
- if (matte != null && alpha != 0 )
616
+ pixels [i ] = pixels [i ] & 0xffffff | ~maskPixels [i ] & 0xff000000 ;
617
+ }
618
+ image .setPixels (pixels , 0 , width , 0 , y , width , 1 );
619
+ }
620
+ }
621
+ else if (matte == null )
622
+ {
623
+ for (int y = 0 ; y < height ; y ++)
624
+ {
625
+ image .getPixels (pixels , 0 , width , 0 , y , width , 1 );
626
+ mask .getPixels (maskPixels , 0 , width , 0 , y , width , 1 );
627
+ for (int x = 0 ; x < width ; x ++)
628
+ {
629
+ if (!isSoft )
612
630
{
613
- float k = alpha / 255F ;
614
- r = clampColor (((r / 255f - matte [0 ]) / k + matte [0 ]) * 255 );
615
- g = clampColor (((g / 255f - matte [1 ]) / k + matte [1 ]) * 255 );
616
- b = clampColor (((b / 255f - matte [2 ]) / k + matte [2 ]) * 255 );
631
+ maskPixels [x ] ^= -1 ;
617
632
}
633
+ pixels [x ] = pixels [x ] & 0xffffff | maskPixels [x ] & 0xff000000 ;
618
634
}
619
- else
635
+ image .setPixels (pixels , 0 , width , 0 , y , width , 1 );
636
+ }
637
+ }
638
+ else
639
+ {
640
+ // Original code is to clamp component and alpha to [0f, 1f] as matte is,
641
+ // and later expand to [0; 255] again (with rounding).
642
+ // component = 255f * ((component / 255f - matte) / (alpha / 255f) + matte)
643
+ // = (255 * component - 255 * 255f * matte) / alpha + 255f * matte
644
+ // There is a clearly visible factor 255 for most components in above formula,
645
+ // i.e. max value is 255 * 255: 16 bits + sign.
646
+ // Let's use faster fixed point integer arithmetics with Q16.15,
647
+ // introducing neglible errors (0.001%).
648
+ // Note: For "correct" rounding we increase the final matte value (m0h, m1h, m2h) by
649
+ // a half an integer.
650
+ final int fraction = 15 ;
651
+ final int factor = 255 << fraction ;
652
+ final int m0 = Math .round (factor * matte [0 ]) * 255 ;
653
+ final int m1 = Math .round (factor * matte [1 ]) * 255 ;
654
+ final int m2 = Math .round (factor * matte [2 ]) * 255 ;
655
+ final int m0h = m0 / 255 + (1 << fraction - 1 );
656
+ final int m1h = m1 / 255 + (1 << fraction - 1 );
657
+ final int m2h = m2 / 255 + (1 << fraction - 1 );
658
+ for (int y = 0 ; y < height ; y ++)
659
+ {
660
+ image .getPixels (pixels , 0 , width , 0 , y , width , 1 );
661
+ mask .getPixels (maskPixels , 0 , width , 0 , y , width , 1 );
662
+ for (int x = 0 ; x < width ; x ++)
620
663
{
621
- alpha = 255 - Color .alpha (alphaRow [x ]);
664
+ int a = Color .alpha (maskPixels [x ]);
665
+ if (a == 0 )
666
+ {
667
+ pixels [x ] = pixels [x ] & 0xffffff ;
668
+ continue ;
669
+ }
670
+ int rgb = pixels [x ];
671
+ int r = Color .red (rgb );
672
+ int g = Color .green (rgb );
673
+ int b = Color .blue (rgb );
674
+ r = clampColor (((r * factor - m0 ) / a + m0h ) >> fraction );
675
+ g = clampColor (((g * factor - m1 ) / a + m1h ) >> fraction );
676
+ b = clampColor (((b * factor - m2 ) / a + m2h ) >> fraction );
677
+ pixels [x ] = Color .argb (a , r , g , b );
622
678
}
623
-
624
- destRow [x ] = Color .argb (alpha , r , g , b );
679
+ image .setPixels (pixels , 0 , width , 0 , y , width , 1 );
625
680
}
626
- masked .setPixels (destRow , 0 , width , 0 , y , width , 1 );
627
681
}
628
- return masked ;
682
+ return image ;
629
683
}
630
684
631
685
private int clampColor (float color )
632
686
{
633
- return color < 0 ? 0 : (color > 255 ? 255 : Math .round (color ));
687
+ // Float.valueOf is no need and it is too slow
688
+ if (color <= 0 )
689
+ {
690
+ return 0 ;
691
+ }
692
+ else if (color >= 255 )
693
+ {
694
+ return 255 ;
695
+ }
696
+ return (int )color ;
634
697
}
635
698
636
699
/**
0 commit comments