/*
 * Copyright (c) 2006, 2007, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

/**
 * @test
 * @bug     6299168 6399660 6519600
 * @summary Test verifies that the subsampling usage does not causes color
 *          changes.
 * @run     main BMPSubsamplingTest
 * @author  andrew.brygin
 */
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DirectColorModel;
import java.awt.image.IndexColorModel;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.IOException;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageOutputStream;


public class BMPSubsamplingTest {
    private static final int TYPE_INT_GRB = 0x100;
    private static final int TYPE_INT_GBR = 0x101;
    private static final int TYPE_INT_RBG = 0x102;
    private static final int TYPE_INT_BRG = 0x103;
    private static final int TYPE_3BYTE_RGB = 0x104;
    private static final int TYPE_3BYTE_GRB = 0x105;
    private static final int TYPE_USHORT_555_GRB = 0x106;
    private static final int TYPE_USHORT_555_BGR = 0x107;
    private static final int TYPE_USHORT_565_BGR = 0x108;
    private static final int TYPE_4BPP_INDEXED = 0x109;

    private String format = "BMP";

    private int[] img_types = new int[] {
        BufferedImage.TYPE_INT_RGB,
        BufferedImage.TYPE_INT_BGR,
        TYPE_INT_GRB,
        TYPE_INT_GBR,
        TYPE_INT_RBG,
        TYPE_INT_BRG,
        BufferedImage.TYPE_USHORT_555_RGB,
        BufferedImage.TYPE_USHORT_565_RGB,
        TYPE_USHORT_555_GRB,
        TYPE_USHORT_555_BGR,
        TYPE_USHORT_565_BGR,
        BufferedImage.TYPE_3BYTE_BGR,
        TYPE_3BYTE_RGB,
        TYPE_3BYTE_GRB,
        BufferedImage.TYPE_BYTE_INDEXED,
        TYPE_4BPP_INDEXED
    };
    Color[] colors = new Color[] { Color.red, Color.green, Color.blue };

    private final int srcXSubsampling = 3;
    private final int srcYSubsampling = 3;

    int dx = 300;
    int h = 300;
    int w = dx * colors.length + srcXSubsampling;



    public BMPSubsamplingTest() throws IOException {
        ImageWriter writer =
            ImageIO.getImageWritersByFormatName(format).next();

        ImageWriteParam wparam = writer.getDefaultWriteParam();
        wparam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
        String[] types = wparam.getCompressionTypes();
        for (int t = 0; t < img_types.length; t++) {
            int img_type = img_types[t];
            System.out.println("Test for " + getImageTypeName(img_type));
            BufferedImage image = getTestImage(img_type);

            ImageTypeSpecifier specifier = new ImageTypeSpecifier(image);

            if (!writer.getOriginatingProvider().canEncodeImage(specifier)) {
                System.out.println("Writer does not support encoding this buffered image type.");
                continue;
            }

            for(int i=0; i<types.length; i++) {
                if ("BI_JPEG".equals(types[i])) {
                    // exclude BI_JPEG from automatic test
                    // due to color diffusion effect on the borders.
                    continue;
                }

                if (canEncodeImage(types[i], specifier, img_type)) {
                    System.out.println("compression type: " + types[i] +
                        " Supported for " + getImageTypeName(img_type));
                } else {
                    System.out.println("compression type: " + types[i] +
                        " NOT Supported for " + getImageTypeName(img_type));
                    continue;
                }
                ImageWriteParam imageWriteParam = getImageWriteParam(writer, types[i]);

                imageWriteParam.setSourceSubsampling(srcXSubsampling,
                                                     srcYSubsampling,
                                                     0, 0);
                File outputFile = new File("subsampling_test_" +
                    getImageTypeName(img_type) + "__" +
                    types[i] + ".bmp");
                ImageOutputStream ios =
                    ImageIO.createImageOutputStream(outputFile);
                writer.setOutput(ios);

                IIOImage iioImg = new IIOImage(image, null, null);

                writer.write(null, iioImg, imageWriteParam);

                ios.flush();
                ios.close();

                BufferedImage outputImage = ImageIO.read(outputFile);
                checkTestImage(outputImage);
            }
        }
    }

    private ImageWriteParam getImageWriteParam(ImageWriter writer, String value) {
        ImageWriteParam imageWriteParam = writer.getDefaultWriteParam();
        imageWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
        imageWriteParam.setCompressionType(value);
        return imageWriteParam;
    }


    private boolean canEncodeImage(String compression,
                                   ImageTypeSpecifier imgType, int rawType)
    {
        int biType = imgType.getBufferedImageType();
        if ((!compression.equals("BI_BITFIELDS")) &&
            ((rawType == BufferedImage.TYPE_USHORT_565_RGB) ||
             (rawType == TYPE_USHORT_565_BGR)))
        {
            return false;
        }

        int bpp = imgType.getColorModel().getPixelSize();
        if (compression.equals("BI_RLE4") && bpp != 4) {
            // only 4bpp images can be encoded as BI_RLE4
            return false;
        }
        if (compression.equals("BI_RLE8") && bpp != 8) {
            // only 8bpp images can be encoded as BI_RLE8
            return false;
        }

        if (compression.equals("BI_PNG") &&
            ((rawType == TYPE_USHORT_555_GRB) ||
             (rawType == TYPE_USHORT_555_BGR)))
        {
            return false;
        }

        return true;
    }

    private String getImageTypeName(int t) {
        switch(t) {
          case BufferedImage.TYPE_INT_RGB:
              return "TYPE_INT_RGB";
          case BufferedImage.TYPE_INT_BGR:
              return "TYPE_INT_BGR";
          case TYPE_INT_GRB:
              return "TYPE_INT_GRB";
          case TYPE_INT_GBR:
              return "TYPE_INT_GBR";
          case TYPE_INT_RBG:
              return "TYPE_INT_RBG";
          case TYPE_INT_BRG:
              return "TYPE_INT_BRG";
          case BufferedImage.TYPE_USHORT_555_RGB:
              return "TYPE_USHORT_555_RGB";
          case BufferedImage.TYPE_USHORT_565_RGB:
              return "TYPE_USHORT_565_RGB";
          case TYPE_USHORT_555_GRB:
              return "TYPE_USHORT_555_GRB";
          case TYPE_USHORT_555_BGR:
              return "TYPE_USHORT_555_BGR";
          case TYPE_USHORT_565_BGR:
              return "TYPE_USHORT_565_BGR";
          case BufferedImage.TYPE_3BYTE_BGR:
              return "TYPE_3BYTE_BGR";
          case TYPE_3BYTE_RGB:
              return "TYPE_3BYTE_RGB";
          case TYPE_3BYTE_GRB:
              return "TYPE_3BYTE_GRB";
          case BufferedImage.TYPE_BYTE_INDEXED:
              return "TYPE_BYTE_INDEXED";
          case TYPE_4BPP_INDEXED:
              return "TYPE_BYTE_INDEXED (4bpp)";
          default:
              throw new IllegalArgumentException("Unknown image type: " + t);
        }
    }

    private BufferedImage getTestImage(int type) {
        BufferedImage dst = null;
        ColorModel colorModel = null;
        WritableRaster raster = null;
        switch(type) {
          case TYPE_INT_GRB:
              colorModel = new DirectColorModel(24,
                                                0x0000ff00,
                                                0x00ff0000,
                                                0x000000ff);
              break;
          case TYPE_INT_GBR:
              colorModel = new DirectColorModel(24,
                                                0x000000ff,
                                                0x00ff0000,
                                                0x0000ff00);
              break;
          case TYPE_INT_RBG:
              colorModel = new DirectColorModel(24,
                                                0x00ff0000,
                                                0x000000ff,
                                                0x0000ff00);
              break;
          case TYPE_INT_BRG:
              colorModel = new DirectColorModel(24,
                                                0x0000ff00,
                                                0x000000ff,
                                                0x00ff0000);
                break;
          case TYPE_3BYTE_RGB:
              dst = create3ByteImage(new int[] {8, 8, 8},
                                     new int[] {0, 1, 2});
              break;
          case TYPE_3BYTE_GRB:
              dst = create3ByteImage(new int[] {8, 8, 8},
                                     new int[] {1, 0, 2});
              break;
          case TYPE_USHORT_555_GRB:
              colorModel = new DirectColorModel(16,
                                                0x03E0,
                                                0x7C00,
                                                0x001F);
              break;
          case TYPE_USHORT_555_BGR:
              colorModel = new DirectColorModel(16,
                                                0x001F,
                                                0x03E0,
                                                0x7C00);
              break;
          case TYPE_USHORT_565_BGR:
              colorModel = new DirectColorModel(16,
                                                0x001F,
                                                0x07E0,
                                                0xf800);
              break;
          case TYPE_4BPP_INDEXED:
              dst = createIndexImage(4);
              break;
          default:
              dst = new BufferedImage(w, h, type);
        }
        if (dst == null) {
            raster = colorModel.createCompatibleWritableRaster(w, h);
            dst = new BufferedImage(colorModel, raster, false, null);
        }
        Graphics2D g = dst.createGraphics();
        for (int i = 0; i < colors.length; i++) {
            g.setColor(colors[i]);
            g.fillRect(i * dx, 0, dx, h);
        }
        g.dispose();
        return dst;
    }

    private BufferedImage createIndexImage(int bpp) {
        // calculate palette size
        int psize = (1 << bpp);

        // prepare palette;
        byte[] r = new byte[psize];
        byte[] g = new byte[psize];
        byte[] b = new byte[psize];

        for (int i = 0; i < colors.length; i++) {
            r[i] = (byte)(0xff & colors[i].getRed());
            g[i] = (byte)(0xff & colors[i].getGreen());
            b[i] = (byte)(0xff & colors[i].getBlue());
        }

        // now prepare appropriate index clor model
        IndexColorModel icm = new IndexColorModel(bpp, psize, r, g, b);

        return new BufferedImage(w, h, BufferedImage.TYPE_BYTE_INDEXED, icm);
    }

    private BufferedImage create3ByteImage(int[] nBits, int[] bOffs) {
        ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
        ColorModel colorModel =
            new ComponentColorModel(cs, nBits,
                                    false, false,
                                    Transparency.OPAQUE,
                                    DataBuffer.TYPE_BYTE);
        WritableRaster raster =
            Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
                                           w, h,
                                           w*3, 3,
                                           bOffs, null);
        return new BufferedImage(colorModel, raster, false, null);
    }

    private void checkTestImage(BufferedImage dst) {
        // NB: do not forget about subsampling factor.
        int x = dx / (2 * srcXSubsampling);
        int y = h / (2 * srcYSubsampling);
        System.out.println("Check result: width=" + dst.getWidth() +
            ", height=" + dst.getHeight());
        for (int i = 0; i < colors.length; i++) {
            System.out.println("\tcheck at: " + x + ", " + y);
            int srcRgb = colors[i].getRGB();
            int dstRgb = dst.getRGB(x, y);
            if (srcRgb != dstRgb) {
                throw new RuntimeException("Test failed due to wrong dst color " +
                    Integer.toHexString(dstRgb) + " at " + x + "," + y +
                    "instead of " + Integer.toHexString(srcRgb));
            }
            x += dx / srcXSubsampling;
        }
    }

    public static void main(String args[]) throws IOException {
        BMPSubsamplingTest test = new BMPSubsamplingTest();
    }
}