@@ -563,74 +563,137 @@ public Bitmap getOpaqueImage() throws IOException
563563 return SampledImageReader .getRGBImage (this , null );
564564 }
565565
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 )
570577 {
571578 if (mask == null )
572579 {
573580 return image ;
574581 }
575582
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 () );
578585
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.
580589 if (mask .getWidth () < width || mask .getHeight () < height )
581590 {
582591 mask = scaleImage (mask , width , height );
583592 }
584-
585- if (mask .getWidth () > width || mask .getHeight () > height )
593+ if (image .getWidth () < width || image .getHeight () < height )
586594 {
587- width = mask .getWidth ();
588- height = mask .getHeight ();
589595 image = scaleImage (image , width , height );
590596 }
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 ];
591603
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 ())
600609 {
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 ++)
604611 {
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 --)
609615 {
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 )
612630 {
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 ;
617632 }
633+ pixels [x ] = pixels [x ] & 0xffffff | maskPixels [x ] & 0xff000000 ;
618634 }
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 ++)
620663 {
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 );
622678 }
623-
624- destRow [x ] = Color .argb (alpha , r , g , b );
679+ image .setPixels (pixels , 0 , width , 0 , y , width , 1 );
625680 }
626- masked .setPixels (destRow , 0 , width , 0 , y , width , 1 );
627681 }
628- return masked ;
682+ return image ;
629683 }
630684
631685 private int clampColor (float color )
632686 {
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 ;
634697 }
635698
636699 /**
0 commit comments