3

I am working with the Java class RescaleOp to change the brightness of BufferedImage instances. The alpha channel consistently causes problems. See refs below -- thanks to @trashgod for his impressive Java2D insights.

The docs from RescaleOp clearly state for instances of BufferedImage, alpha channel is not scaled in the single factor constructors -- I interpret as either float or float[1].

Quote from JDK6: (emphasis added)

For BufferedImages, rescaling operates on color and alpha components. The number of sets of scaling constants may be one, in which case the same constants are applied to all color (but not alpha) components. Otherwise, the number of sets of scaling constants may equal the number of Source color components, in which case no rescaling of the alpha component (if present) is performed. If neither of these cases apply, the number of sets of scaling constants must equal the number of Source color components plus alpha components, in which case all color and alpha components are rescaled.

For a BufferedImage with type BufferedImage.TYPE_INT_ARGB, there are four channels (R-G-B-A), where alpha is the last channel. (Why didn't they call it BufferedImage.TYPE_INT_RGBA?) I tried these RescaleOp transformations without success: (assume float scaleFactor = 1.25f and float offset = 0.0f)

new RescaleOp(scaleFactor, offset, (RenderingHints) null)

new RescaleOp(new float[] { scaleFactor },
              new float[] { offset },
              (RenderingHints) null)

new RescaleOp(new float[] { scaleFactor, scaleFactor, scaleFactor },
              new float[] { offset, offset, offset },
              (RenderingHints) null)

Only this works: (assume float alphaScaleFactor = 1.0f)

new RescaleOp(new float[] { scaleFactor, scaleFactor, scaleFactor, alphaScaleFactor },
              new float[] { offset, offset, offset, offset },
              (RenderingHints) null)
  1. Do I misunderstand the official JDK docs?
  2. Or, is this a bug that could/should be fixed in future JDKs?
  3. Is there a way to find (at runtime) the alpha channel index?
    • Methods that might help:
      1. ColorModel BufferedImage.getColorModel()
      2. int ColorModel.getNumColorComponents()
      3. boolean ColorModel.hasAlpha()
      4. int ColorModel.getNumComponents() (may include optional alpha channel)
      5. ColorSpace ColorModel.getColorSpace()

Please advise.

Community
  • 1
  • 1
kevinarpe
  • 20,319
  • 26
  • 127
  • 154
  • 1
    Q: "Why didn't they call it BufferedImage.TYPE_INT_RGBA?" A: Because the image type is really ARGB (in that order) packed in an `int`. `RescaleOp` has a different order of the components for convenience because most often you don't want to rescale the alpha component. Also, it must work with all types of images (ABGR, RGBA, etc). :-) – Harald K Aug 15 '13 at 17:32

2 Answers2

1

RescaleOp offers two modes of operation on rasters having a direct color model. The two modes correspond to the two constructors.

  1. RescaleOp(float scaleFactor, float offset, RenderingHints hints), illustrated here, scales all color components by the same factor and leaves the alpha channel unchanged.

  2. RescaleOp(float[] scaleFactors, float[] offsets, RenderingHints hints), illustrated in the second example cited, requires that "the number of sets of scaling constants must equal the number of source color components plus alpha component." A BufferedImage of type TYPE_INT_ARGB has three color components plus one alpha component, so the scaleFactors array must have four components.

The difference is convenience. The inner loop steps through the array accordingly.

int step = 0;
…
if (length > 1) {
    step = 1;
}

In summary, your first example should work; your second and third examples should throw an IllegalArgumentException; and your fourth works as advertised.

Community
  • 1
  • 1
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
1

Quotes from the API doc:

The number of sets of scaling constants may be one, in which case the same constants are applied to all color (but not alpha) components.

Works as documented.

Otherwise, the number of sets of scaling constants may equal the number of Source color components, in which case no rescaling of the alpha component (if present) is performed.

Only works if there's no alpha component present in the source. Not as documented.

If neither of these cases apply, the number of sets of scaling constants must equal the number of Source color components plus alpha components, in which case all color and alpha components are rescaled

Works as documented.

Consider this test case:

public class RescaleOpTest {
    public static void main(String[] args) {
        BufferedImage rgb = new BufferedImage(10, 10, BufferedImage.TYPE_INT_RGB);
        // All ok
        new RescaleOp(new float[] {1}, new float[] {1}, null).filter(rgb, rgb);
        new RescaleOp(new float[] {1, 1, 1}, new float[] {1, 1, 1}, null).filter(rgb, rgb);

        BufferedImage argb = new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB);
        new RescaleOp(new float[] {1}, new float[] {1}, null).filter(argb, argb); // Ok
        new RescaleOp(new float[] {1, 1, 1, 1}, new float[] {1, 1, 1, 1}, null).filter(argb, argb); // Ok

        WritableRaster argbRaster = argb.getRaster();
        new RescaleOp(new float[] {1}, new float[] {1}, null).filter(argbRaster, argbRaster); // Ok
        new RescaleOp(new float[] {1, 1, 1, 1}, new float[] {1, 1, 1, 1}, null).filter(argbRaster, argbRaster); // Ok

        WritableRaster rgbRaster = argbRaster.createWritableChild(0, 0, argb.getWidth(), argb.getHeight(), 0, 0, new int[] {0, 1, 2});
        new RescaleOp(new float[] {1}, new float[] {1}, null).filter(rgbRaster, rgbRaster); // Ok
        new RescaleOp(new float[] {1, 1, 1}, new float[] {1, 1, 1}, null).filter(rgbRaster, rgbRaster); // Ok

        new RescaleOp(new float[] {1, 1, 1}, new float[] {1, 1, 1}, null).filter(argb, argb); // Boom, even though it's the same image, so rasters can't possibly be wrong...
    }
}

I'd say it's a bug, and your code should work. Can't remember if I filed it, or if there was already a matching bug report at the time I ran across it. But don't hold your breath waiting for Oracle to fix it... The workaround is to always have as many scale factors/offsets as there's components/bands in the raster (or use a child raster to mask away the alpha, as seen in the code above).

To find the index of the alpha sample, look at the SampleModel. I don't think you'll need it for RescaleOp, because the order is always colors first, then alpha (ie, R,G,B,A always, regardless of ARGB, ABGR, RGBA etc). Anyway, dependent on the type of SampleModel you have (yes, unfortunately, you have to cast):

  • For SinglePixelPackedSampleModel:

    • SinglePixelPackedSampleModel.getBitMasks() (you can compute the bit offset/bit size from this)
    • SinglePixelPackedSampleModel.getBitOffsets() (but you'll need to get the bit size somehow, or assume it's 8 bit)
  • For ComponentSampleModel: ComponentSampleModel.getBandOffsets().

Harald K
  • 26,314
  • 7
  • 65
  • 111