2007年1月2日星期二

Java_003:如何给图片增加彩色文字水印?(摘录+整理)

说明:以下程序在JDK 1.7.0 Update 51下测试通过。
程序功能:在图片上增加5种文字水印。
(1)图片左上角:红色高密度的文字水印。
(2)图片右上角:与原图像素同色,但是密度是原像素的2倍。
(3)图片中间:与原图像素同色,但是密度是原像素的一半。
(4)图片左下角:像素的Alpha值是原来像素的一半。
(5)图片右下角:白色高密度的文字水印。
感觉(2)(3)(4)和想象的颜色不一样,不知道是不是对像素的理解有欠缺,仅供各位参考。

1. ImgMod04a.java

/*
 Copyright 2006, R.G.Baldwin

 This is a modification to the class named ImgMod04 that eliminates the use of a second image-processing method.
 Otherwise, it is identical to the class named ImgMod04.

 Note that some of the comments may not have been updated to reflect the modifications.

 The purpose of this program is to make it easy to experiment with the modification of pixel data in an image and to display a modified version of the image along with the original image.

 This program is an update of the earlier program named ImgMod02a.
 This update supports the following new features:

 The ability to write the modified image into an output file in JPEG format.
 The name of the output file is junk.jpg and it is written into the current directory.

 The elimination of several conversions back and forth between type double and type int.
 All computations are now performed as type double, and the data is maintained as type double from the initial conversion from int to double to the point where it is ready to be displayed or written into a JPEG file.

 The Replot button was moved to the top of the display to make it accessible when the display is too long to fit on the screen.
 (For purposes of seeing the entire display in that case, it can be moved up and down on the screen using the right mouse button and the up and down arrow keys.)

 The program extracts the pixel data from an image file into a 3D array of type:

 double[row][column][depth].

 The first two dimensions of the array correspond to the rows and columns of pixels in the image.
 The third dimension always has a value of 4 and contains the following values by index:

 0 alpha
 1 red
 2 green
 3 blue

 Note that these values are stored as type double rather than type unsigned byte which is the format of pixel data in the original image file.
 This type conversion eliminates many problems involving the requirement to perform unsigned arithmetic on unsigned byte data.

 The program supports gif and jpg input files and possibly some other file types as well.
 The output file is always a JPEG file.

 Operation:  This program provides a framework that is designed to invoke another program to process the pixels extracted from an image.
 In other words, this program extracts the pixels and puts them in a format that is relatively easy to work with.
 A second program is invoked to actually process the pixels.
 Typical usage is as follows:

 java ImgMod04a ProcessingProgramName ImageFileName

 For test and illustration purposes, the source code includes a class definition for an image processing program named ProgramTestA.

 If the ImageFileName is not specified on the command line, the program will search for an image file in the current directory named ImgMod04aTest.jpg and will process it using the processing program specified by the second command-line argument.

 If both command-line arguments are omitted, the program will search for an image file in the current directory named ImgMod04aTest.jpg and will process it using the built in processing program named ProgramTestA.
 A complete description of the behavior of the test program is provided by comments in the source code for the class named ProgramTestA.

 The image file must be provided by the user in all cases.
 However, it doesn't have to be in the current directory if a path to the file is specified on the command line.

 When the program is started, the original image and one processed version of the image are displayed in a frame with the original image above the processed image.
 A Replot button appears at the top of the frame.
 If the user clicks the Replot button, the image processing method is re-run on the original image.
 The original image is reprocessed and the new processed version of the image replaces the old version.

 The processing program may provide a  GUI for data input making it possible for the user to modify the behavior of the image processing method each time it is run.
 This capability is illustrated in the built-in processing program named ProgramTestA.

 The image processing program must implement the
 interface named ImgIntfc04a.  That interface declares an
 image processing method with the following signature:

 double[][][]  processImg(double[][][] input);

 The image processing method must return a reference to a 3D double array object.

 The processing method receives a reference to a 3D double array object containing image pixel data in the format described earlier.

 The image processing program cannot use a parameterized constructor.
 This is because an object of the class is instantiated by invoking the newInstance method of the class named Class on the name of the image processing program provided as a String on the command line.
 This approach to object instantiation does not support parameterized constructors.

 If the image processing program has a main method, it will be ignored.

 The processImg method receive a 3D array containing pixel data.
 It should make a copy of the incoming array and modify the copy rather than modifying the original.
 Then the method should return a reference to the modified copy of the 3D pixel array.

 The processImg method is free to modify the values of the pixels in the incoming array in any manner before returning reference to the modified array.
 Note however that native pixel data consists of four unsigned bytes.
 If the modification of the pixel data produces negative values or positive value greater than 255, this should be dealt with before returning the modified pixel data.
 Otherwise, the results of displaying the modified pixel data may not be as expected.

 There are at least two ways to deal with this situation.
 One way is to simply clamp all negative values at zero and to clamp all values greater than 255 at 255.
 The other way is to perform a further modification so as to map the range from -x to +y into the range from 0 to 255.
 There is no single correct way for all situations.

 When the processImg method returns, this program causes the original image and the modified image to be displayed in a frame on the screen with the original image above the modified image.

 If the program is unable to load the image file within ten seconds, it will abort with an error message.

 */
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.io.*;
import javax.imageio.*;

class ImgMod04a extends Frame {

    Image rawImg; // A reference to the raw image.
    int imgCols;  // Number of horizontal pixels
    int imgRows; // Number of rows of pixels
    Image modImg; // Reference to modified image

    // Default image processing program.
    // This class will be executed to process the image if the name of another class is not entered on the command line.
    // Note that the source code for this class file is included in this source code file.
    static String theProcessingClass = "ProgramTestA";

    // Default image file name.
    // This image file will be processed if another file name is not entered on the command line.
    // You must provide this file in the current directory if it is going to be processed.
    static String theImgFile = "ImgMod04aTest.jpg";

    MediaTracker tracker;
    Display display = new Display(); // A Canvas object
    Button replotButton = new Button("Replot");

    // Reference to the image processing object.
    ImgIntfc04a imageProcessingObject;
    //-----------------------------------------------------//

    public static void main(String[] args) {
        // Get names for the image processing class and the image file to be processed.
        // Program supports gif files and jpg files and possibly some other file types as well.
        if (args.length == 0) {
        } else if (args.length == 1) {
            theProcessingClass = args[0];
        } else if (args.length == 2) {
            theProcessingClass = args[0];
            theImgFile = args[1];
        } else {
            System.out.println("Invalid args");
            System.exit(1);
        }

        // Display name of processing program and image file.
        System.out.println("Processing program: " + theProcessingClass);
        System.out.println("Image file: " + theImgFile);

        // Instantiate an object of this class.
        ImgMod04a obj = new ImgMod04a();
    }

    public ImgMod04a() {
        // Get an image from the specified image file.
        // Can be in a different directory if the path was entered with the file name on the  command line.
        // This local variable mustbe declared final because it is accessed from within an anonymous inner class.
        final double[][][] threeDPix = getTheImage();

        // Construct the display object.
        this.setTitle("Copyright 2006, Baldwin");
        this.setBackground(Color.YELLOW);
        this.add(display);
        this.add(replotButton, BorderLayout.NORTH);

        // Make the frame visible so as to make it possible to get insets and the height of the button.
        setVisible(true);
        // Get and store inset data for the Frame and the height of the button.
        int inTop = this.getInsets().top;
        int inLeft = this.getInsets().left;
        int buttonHeight = replotButton.getSize().height;

        // Size the frame so that a small amount of yellow background will show on the right, between the images, and on the bottom when both images are displayed, one above the other.
        this.setSize(2 * inLeft + imgCols + 2, inTop
                + buttonHeight + 2 * imgRows + 8);

        // Anonymous inner class listener for Replot button.
        // This actionPerformed method is invoked when the user clicks the Replot button.
        // It is also invoked at startup  when this program posts an ActionEvent to the system event queue attributing the event to the Replot button.
        replotButton.addActionListener(
                new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                        // Pass a 3D array of pixel data to the processing method.
                        // This method returns one 3D array of pixel data.
                        // The contents of output array are displayed as the second image in the display.
                        // The contents of the output array are also written into an output JPEG file named junk.jpg.
                        double[][][] threeDPixModA = imageProcessingObject.processImg(threeDPix);
                       // Now prepare the pixel array for display.

                        // Convert the 3D array of modified pixel data of type double to a 1D array of pixel data of type int.
                        // This 1D array is in a standard pixel format.
                        // See a brief description of the format in the comments in the method named convertTo1D.
                        int[] oneDPixA = convertTo1D(threeDPixModA);
                        // Use the createImage() method of the Component class to create a new Image object from the1D array of pixel data.
                        // Note that MemoryImageSource implements the ImageProducer interface and therefore satisfies one of the overloaded versions of the createImage method.
                        modImg = createImage(new MemoryImageSource(imgCols, imgRows, oneDPixA, 0, imgCols));

                        // Repaint the image display frame with the original image at the top and the modified image at the bottom.
                        display.repaint();

                        // Write the modified image into a JPEG file named junk.jpg.
                        writeJpegFile(modImg, imgCols, imgRows);

                    }
                }
        );

        // Continuing with the constructor code ...
        // Instantiate a new object of the image processing class.
        // Note that this object is instantiated using the newInstance method of the class named Class.
        // This approach does not allow for the use of a parameterized constructor.
        try {
            imageProcessingObject = (ImgIntfc04a) Class.forName(
                    theProcessingClass).newInstance();

            // Post a counterfeit ActionEvent to the system eventqueue and attribute it to the Replot button.
            // (See the anonymous ActionListener class that registers an ActionListener object on the RePlot button above.)
            // Posting this event causes the image processing method to be invoked at startup and causes the modified image to be displayed.
            Toolkit.getDefaultToolkit().getSystemEventQueue().
                    postEvent(
                            new ActionEvent(replotButton,
                                    ActionEvent.ACTION_PERFORMED,
                                    "Replot")
                    );

            // At this point, the image has been processed.
            // The original image and the modified image have been displayed.
            // From this point forward, each time the user clicks the Replot button, the image will be processed again and the new modified image will be displayed along with the original image.
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        }

        // Cause the composite of the frame, the canvas, and the button to become visible.
        this.setVisible(true);

        // Anonymous inner class listener to terminate program.
        this.addWindowListener(
                new WindowAdapter() {
                    public void windowClosing(WindowEvent e) {
                        System.exit(0); // terminate the program
                    }
                }
        );
    }

    // Inner class for canvas object on which to display the two images.
    class Display extends Canvas {

        // Override the paint method to display the rawImg and the modified image on the same Canvas object, separated by a couple of rows of pixels in the background color.
        public void paint(Graphics g) {
            // First confirm that the image has been completely loaded and that none of the image references are null.
            if (tracker.statusID(1, false)
                    == MediaTracker.COMPLETE) {
                if ((rawImg != null)
                        && (modImg != null)) {
                    g.drawImage(rawImg, 0, 0, this);
                    g.drawImage(modImg, 0, imgRows + 2, this);
                }
            }
        }
    }

    // Save pixel values as type double to make arithmetic easier later.
    // The purpose of this method is to convert the data in the int oneDPix array into a 3D array of type double.
    // The dimensions of the 3D array are row, col, and color in that order.
    // Row and col correspond to the rows and columns of the pixels in the image.
    // Color corresponds to transparency and color information at the following index levels in the third dimension:
    // 0 alpha (tranparency)
    // 1 red
    // 2 green
    // 3 blue
    // The structure of this code is determined by the way that the pixel data is formatted into the 1D array of pixel data of type int when the image file is read.
    double[][][] convertTo3D(
            int[] oneDPix, int imgCols, int imgRows) {
        // Create the new 3D array to be populated with pixwl data.
        double[][][] data = new double[imgRows][imgCols][4];

        for (int row = 0; row < imgRows; row++) {
            // Extract a row of pixel data into a temporary array of ints
            int[] aRow = new int[imgCols];
            for (int col = 0; col < imgCols; col++) {
                int element = row * imgCols + col;
                aRow[col] = oneDPix[element];
            }

            // Move the data into the 3D array.
            // Note the use of bitwise AND and bitwise right shift operations to mask all but the correct set of eight bits.
            for (int col = 0; col < imgCols; col++) {
                // Alpha data
                data[row][col][0] = (aRow[col] >> 24) & 0xFF;
                // Red data
                data[row][col][1] = (aRow[col] >> 16) & 0xFF;
                // Green data
                data[row][col][2] = (aRow[col] >> 8) & 0xFF;
                // Blue data
                data[row][col][3] = (aRow[col]) & 0xFF;
            }
        }
        return data;
    }

    // The purpose of this method is to convert the data in the 3D array of type double back into the a 1d array of type int.
    // This is the reverse of the method named convertTo3D.
    // Note that the data values are clamped at 0 and 255 before casting to type int.
    int[] convertTo1D(double[][][] data) {
        int imgRows = data.length;
        int imgCols = data[0].length;

        // Create the 1D array of type int to be populated with pixel data, one int value per pixel, with four color and alpha bytes per int value.
        int[] oneDPix = new int[imgCols * imgRows * 4];

        // Move the data into the 1D array.
        // Note the use of the bitwise OR operator and the bitwise left-shift operators to put the four 8-bit bytes into each int.
        // Also note that the values are clamped at 0 and 255.
        for (int row = 0, cnt = 0; row < imgRows; row++) {
            for (int col = 0; col < imgCols; col++) {
                if (data[row][col][0] < 0) {
                    data[row][col][0] = 0;
                }
                if (data[row][col][0] > 255) {
                    data[row][col][0] = 255;
                }
                if (data[row][col][1] < 0) {
                    data[row][col][1] = 0;
                }
                if (data[row][col][1] > 255) {
                    data[row][col][1] = 255;
                }
                if (data[row][col][2] < 0) {
                    data[row][col][2] = 0;
                }
                if (data[row][col][2] > 255) {
                    data[row][col][2] = 255;
                }
                if (data[row][col][3] < 0) {
                    data[row][col][3] = 0;
                }
                if (data[row][col][3] > 255) {
                    data[row][col][3] = 255;
                }

                oneDPix[cnt] = (((int) data[row][col][0] << 24)
                        & 0xFF000000)
                        | (((int) data[row][col][1] << 16)
                        & 0x00FF0000)
                        | (((int) data[row][col][2] << 8)
                        & 0x0000FF00)
                        | (((int) data[row][col][3])
                        & 0x000000FF);
                cnt++;
            }
        }
        return oneDPix;
    }

    // Write the contents of an Image object to a JPEG file named junk.jpg.
    void writeJpegFile(Image img, int width, int height) {
        // Create an off-screen drawable image by calling the createImage method of the Component class.
        // Cast it to type BufferedImage.
        BufferedImage bufferedImg
                = (BufferedImage) createImage(width, height);

        // Call the createGraphics method of the BufferedImage class to create a Graphics2D object, which can be used to draw into the BufferedImage object.
        Graphics2D bufferedGraphics
                = bufferedImg.createGraphics();

        // Call the drawImage method of the Graphics class to draw the image into the off-screen buffer.
        // Pass null as the ImageObserver.
        bufferedGraphics.drawImage(img, 0, 0, null);

        try {
            // Get a file output stream.
            FileOutputStream outStream
                    = new FileOutputStream("junk.jpg");
            // Call the write method of the ImageIO class to write the contents of the BufferedImage object to an output file in JPEG format.
            ImageIO.write(bufferedImg, "jpeg", outStream);
            outStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // This method reads an image from a specified image file and converts the pixel data into a 3D array of type double.
    // The name of the image file is specified as a String by theImgFile.
    double[][][] getTheImage() {
        rawImg = Toolkit.getDefaultToolkit().
                getImage(theImgFile);

        // Use a MediaTracker object to block until the image is loaded or ten seconds has elapsed.
        tracker = new MediaTracker(this);
        tracker.addImage(rawImg, 1);

        try {
            if (!tracker.waitForID(1, 10000)) {
                System.out.println("Load error.");
                System.exit(1);
            }//end if
        } catch (InterruptedException e) {
            e.printStackTrace();
            System.exit(1);
        }

        // Make certain that the file was successfully loaded.
        if ((tracker.statusAll(false)
                & MediaTracker.ERRORED
                & MediaTracker.ABORTED) != 0) {
            System.out.println("Load errored or aborted");
            System.exit(1);
        }

        // Raw image has been loaded.
        // Get width and height ofthe raw image.
        imgCols = rawImg.getWidth(this);
        imgRows = rawImg.getHeight(this);

        // Create a 1D array object to receive the pixel representation of the image
        int[] oneDPix = new int[imgCols * imgRows];

        // Create an empty BufferedImage object
        BufferedImage buffImage = new BufferedImage(
                imgCols,
                imgRows,
                BufferedImage.TYPE_INT_ARGB);

        // Draw Image into BufferedImage
        Graphics g = buffImage.getGraphics();
        g.drawImage(rawImg, 0, 0, null);

        // Convert the BufferedImage to numeric pixel representation.
        DataBufferInt dataBufferInt
                = (DataBufferInt) buffImage.getRaster().
                getDataBuffer();
        oneDPix = dataBufferInt.getData();

        // Convert the pixel byte data in the 1D array to double data in a 3D array to make it easier to work with the pixel data later.
        // Recall that pixel data is unsigned byte data and Java does not support unsigned arithmetic.
        // Performing unsigned arithmetic on byte data is particularly cumbersome.
        return convertTo3D(oneDPix, imgCols, imgRows);
    }
}
// The ProgramTestA class
// The purpose of this class is to provide a simple example of an image processing class that is compatible with the use of the class program named ImgMod04a and the interface named ImgIntfc04a.
// The constructor for the class displays a small frame on the screen with a single textfield.
// The purpose of the text field is to allow the user to enter a value that represents the slope of a line.
// In operation, the user types a value into the text field and then clicks the Replot button on the main image display frame.
// The user is not required to press the Enter key after typing the new value, but it doesn't do any harm to do so.
// Negative slopes are not supported.
// An attempt to usea negative slope will cause the program to abort with an error.
// The method named processImage receives a 3D array of type double containing alpha, red, green, and blue values for an image.
// The 3D array that is received by processImg is modified my the method to cause a white diagonal line to be drawn down and to the right from the upper left corner of the image.
// The slope of the line is controlled by the value in the text field.
// Initially, this value is 1.0, but the value can be modified by the user.
// (If the characters in the text field cannot be converted to a numeric type double, the program will abort with an error.)
// After drawing the line, the method inverts the colors in the image.
// This results in a black line on an image with inverted colors.
// The method named processImg is required to return one reference to a 3D array objects of type double
// To cause a new line to be drawn, type a new slope value into the text field and click the Replot button at the top of the main image display frame.
//This class extends Frame.
// However, a compatible class is not required to extend the Frame class.
// This example extends Frame because it provides a GUI for user data input.
// A compatible class is required to implement the interface named ImgIntfc04a.

class ProgramTestA extends Frame implements ImgIntfc04a {

    double slope; // Controls the slope of the line
    String inputData; // Obtained via the TextField
    TextField inputField; // Reference to TextField

    // Constructor must take no parameters
    ProgramTestA() {
        // Create and display the user-input GUI.
        setLayout(new FlowLayout());

        Label instructions = new Label(
                "Type a slope value and Replot.");
        add(instructions);

        inputField = new TextField("1.0", 5);
        add(inputField);

        setTitle("Copyright 2006, Baldwin");
        setBounds(400, 0, 200, 100);
        setVisible(true);
    }

    // The following method must be defined to implement the ImgIntfc04a interface.
    public double[][][] processImg(double[][][] threeDPix) {

        // Determine number of rows and cols
        int imgRows = threeDPix.length;
        int imgCols = threeDPix[0].length;

        // Display some interesting information
        System.out.println("Program test");
        System.out.println("Width = " + imgCols);
        System.out.println("Height = " + imgRows);
        // Make a working copy of the 3D array to avoid making permanent changes to the image data.
        double[][][] temp3D = copy3DArray(threeDPix);
        // Get slope value from the TextField
        slope = Double.parseDouble(inputField.getText());

        // Draw a white diagonal line on the image.
        for (int col = 0; col < imgCols; col++) {
            int row = (int) (slope * col);
            if (row > imgRows - 1) {
                break;
            }
            // Set values for alpha, red, green, and blue colors.
            temp3D[row][col][0] = 255.0;
            temp3D[row][col][1] = 255.0;
            temp3D[row][col][2] = 255.0;
            temp3D[row][col][3] = 255.0;

        }
        // Invert the colors.  Do not invert the alpha value.
        invertColors(temp3D);
        // Return two references to the same array of image data.
        return temp3D;
    }

    // This method copies a double version of a 3D pixel array to an new pixel array of type double.
    double[][][] copy3DArray(double[][][] threeDPix) {
        int imgRows = threeDPix.length;
        int imgCols = threeDPix[0].length;

        double[][][] new3D = new double[imgRows][imgCols][4];
        for (int row = 0; row < imgRows; row++) {
            for (int col = 0; col < imgCols; col++) {
                new3D[row][col][0] = threeDPix[row][col][0];
                new3D[row][col][1] = threeDPix[row][col][1];
                new3D[row][col][2] = threeDPix[row][col][2];
                new3D[row][col][3] = threeDPix[row][col][3];
            }
        }
        return new3D;
    }

    // This method inverts the colors in a double version of a 3D array of pixel data.
    // It doesn't invert the alpha value.
    void invertColors(double[][][] data3D) {
        int imgRows = data3D.length;
        int imgCols = data3D[0].length;
        // Invert the colors.  Don't invert the alpha value.
        for (int row = 0; row < imgRows; row++) {
            for (int col = 0; col < imgCols; col++) {
                data3D[row][col][1] = 255 - data3D[row][col][1];
                data3D[row][col][2] = 255 - data3D[row][col][2];
                data3D[row][col][3] = 255 - data3D[row][col][3];
            }
        }
    }
}

2. ImgMod36.java

/* 
 Copyright 2006, R.G.Baldwin

 There are many different ways to add watermarks to an image.  
 The purpose of this program is to illustrate the creation of five different types of visible watermarks:

 1. A high intensity watermark in a single color plane (red).
 2. A watermark that is the same color as the original image pixels but twice as intense.
 3. A watermark that is the same color as the original image pixels but with only half the intensity.
 4. A watermark for which the alpha (transparency) value of the pixels is half of the original values.
 5. A high intensity white watermark.

 This program is designed to be driven by the image processing framework named ImgMod04a.  
 To run this program, enter the following at the command line.

 java ImgMod04a ImgMod36 ImageFileName

 where ImageFileName is the name of a .gif or .jpg file, including the extension.

 The program displays a single frame on the screen.  
 The frame contains the original image at the top and a replica of the original image with the watermarks added at the bottom.  
 The frame also contains a Replot button.
 However, because the program does not allow the user to enter parameters to modify the behavior of the program at runtime, clicking the Replot button has little or no beneficial effect.

 Each time that the program is run, or the Replot button is clicked, the final image containing the watermarks is written into a JPEG file named junk.jpg.  
 If a file having that name already exists in the current directory, it is overwritten.

 This program contains an image processing method named processImg, which is executed by the program named ImgMod04a.
 The method named processImg modifies the image pixels in five selected locations to add the watermarks described above.  
 Then it returns the modified image, which is displayed by the program named ImgMod04a.

 This program requires access to the following class files plus some inner classes that are defined inside the following classes:

 ImgIntfc04a.class
 ImgMod04a.class
 ImgMod36.class

*/
class ImgMod36 implements ImgIntfc04a {

    // This method is required by ImgIntfc04a. 
    // It is called at the beginning of the run and each time thereafter that the user clicks the Replot button on the Frame containing the images.  
    // However, because this program doesn't provide for user input, pressing the Replot button is of no value.  
    // It just displays the same images again.
    public double[][][] processImg(double[][][] threeDPix) {

        int imgRows = threeDPix.length;
        int imgCols = threeDPix[0].length;

        // Make a working copy of the 3D pixel array to avoid making permanent changes to the original image data.
        double[][][] workingCopy = copy3DArray(threeDPix);

        // Declare a working plane.
        double[][] workingPlane;

        // Extract and process the alpha plane.
        workingPlane = extractPlane(workingCopy, 0);
        addWatermark(workingPlane, 0);
        // Insert the alpha plane back into the working array.
        insertPlane(workingCopy, workingPlane, 0);

        // Extract and process the red color plane.
        workingPlane = extractPlane(workingCopy, 1);
        addWatermark(workingPlane, 1);
        insertPlane(workingCopy, workingPlane, 1);

        // Extract and process the green color plane.
        workingPlane = extractPlane(workingCopy, 2);
        addWatermark(workingPlane, 2);
        insertPlane(workingCopy, workingPlane, 2);

        // Extract and process the blue color plane.
        workingPlane = extractPlane(workingCopy, 3);
        addWatermark(workingPlane, 3);
        insertPlane(workingCopy, workingPlane, 3);

        // The alpha plane and all three color planes have now been processed.  
        // The results are stored in the working copy of the original pixel array.
        return workingCopy;
    }

    // The purpose of this method is to extract a color plane from the double version of an image and to return it as a 2D array of type double.
    public double[][] extractPlane(
            double[][][] threeDPixDouble,
            int plane) {

        int numImgRows = threeDPixDouble.length;
        int numImgCols = threeDPixDouble[0].length;

        // Create an empty output array of the same size as a single plane in the incoming array of pixels.
        double[][] output = new double[numImgRows][numImgCols];

        // Copy the values from the specified plane to the double array.
        for (int row = 0; row < numImgRows; row++) {
            for (int col = 0; col < numImgCols; col++) {
                output[row][col]
                        = threeDPixDouble[row][col][plane];
            }
        }
        return output;
    }

    // The purpose of this method is to insert a double 2D plane into the double 3D array that represents an image.
    public void insertPlane(double[][][] threeDPixDouble,
            double[][] colorPlane,
            int plane) {

        int numImgRows = threeDPixDouble.length;
        int numImgCols = threeDPixDouble[0].length;

        // Copy the values from the incoming color plane to the specified plane in the 3D array.
        for (int row = 0; row < numImgRows; row++) {
            for (int col = 0; col < numImgCols; col++) {
                threeDPixDouble[row][col][plane]
                        = colorPlane[row][col];
            }
        }
    }

    // This method copies a double version of a 3D pixel array to an new pixel array of type double.
    double[][][] copy3DArray(double[][][] threeDPix) {
        int imgRows = threeDPix.length;
        int imgCols = threeDPix[0].length;

        double[][][] new3D = new double[imgRows][imgCols][4];
        for (int row = 0; row < imgRows; row++) {
            for (int col = 0; col < imgCols; col++) {
                new3D[row][col][0] = threeDPix[row][col][0];
                new3D[row][col][1] = threeDPix[row][col][1];
                new3D[row][col][2] = threeDPix[row][col][2];
                new3D[row][col][3] = threeDPix[row][col][3];
            }
        }
        return new3D;
    }

    // The purpose of this method is to add watermarks to a 2D array of pixel data. 
    // There are many ways to modify the pixel data to add watermarks.  
    // This method illustrates five of those ways.
    void addWatermark(double[][] plane, int color) {
        int imgRows = plane.length;
        int imgCols = plane[0].length;

        // Create an array containing the basic watermark. 
        // The watermark consists of the characters H2O described by values of 1 and 0.
        int[][] watermark = new int[][]{
            {1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0},
            {1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0},
            {1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1},
            {1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1},
            {1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1},
            {1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1},
            {1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0},
            {1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0},};//end array

        int wmRows = watermark.length;
        int wmCols = watermark[0].length;

        for (int row = 0; row < wmRows; row++) {
            for (int col = 0; col < wmCols; col++) {
                if (watermark[row][col] == 1) {//Ignore 0 values.

                    // Place a high intensity watermark only in the red color plane of the image.  
                    // Place it in the upper left.
                    if (color == 1) { // Modify red plane only.
                        plane[row + 10][col + 10] = 255.0;
                    }

                    // Place a watermark in the upper right area. 
                    // Make the color of the watermark be the same as the color of the image but twice as intense.
                    if (color != 0) {//Don't modify the alpha plane.
                        plane[row + 10][imgCols - wmCols - 10 + col]
                                = plane[row + 10][imgCols - wmCols - 10 + col] * 2.0;
                        plane[row + 10][imgCols - wmCols - 10 + col]
                                = clamp(plane[row + 10][imgCols - wmCols - 10 + col]);
                    }

                    // Place a watermark in the center of the image. 
                    // Make the intensity of each color to be half of the original intensity.
                    if (color != 0) { // Don't modify alpha plane.
                        plane[imgRows - (imgRows / 2 + wmRows / 2) + row][imgCols - (imgCols / 2 + wmCols / 2) + col]
                                = plane[imgRows - (imgRows / 2 + wmRows / 2) + row][imgCols - (imgCols / 2 + wmCols / 2) + col] * 0.5;
                    }

                    // Place a watermark in the lower left.  
                    // Make the transparency value of each pixel to be half of its original value.
                    if (color == 0) { // Modify alpha plane only.
                        plane[imgRows - wmRows - 10 + row][col + 10]
                                = plane[imgRows - wmRows - 10 + row][col + 10] / 2.0;
                    }

                    // Place a high intensity white watermark in the bottom-right.
                    if (color != 0) { // Don't modify the alpha plane.
                        plane[imgRows - wmRows - 10 + row][imgCols - wmCols - 10 + col] = 255.0;
                    }

                }
            }
        }
    }

    // The purpose of this method is to clamp the incoming value to guarantee that it falls in the range from 0 to 255 inclusive.
    double clamp(double data) {
        if (data > 255.0) {
            return 255.0;
        } else if (data < 0.0) {
            return 0.0;
        } else {
            return data;
        }
    }
}

3. ImgIntfc04a.java

/*
 Copyright 2006, R.G.Baldwin

 This is a modification to the interface named ImgIntfc04 that eliminates the use of a second image-processing method. 
 Otherwise, it is identical to the interface named ImgIntfc04.

 The purpose of this interface is to declare the method required by image processing classes that are compatible with the program named ImgMod04a.java.

 */
interface ImgIntfc04a {
    double[][][] processImg(double[][][] input);
}

4. 运行:java ImgMod04a ImgMod36 E:/test.jpeg



参考文献:
1. http://www.developer.com/java/other/article.php/3650011/Processing-Image-Pixels-Creating-Visible-Watermarks-in-Java.htm
2. http://www.developer.com/java/other/article.php/3640776/Processing-Image-Pixels-An-Improved-Image-Processing-Framework-in-Java.htm

没有评论: