• Step By Step(Java 2D图形篇<三>)


        本篇将继续介绍Java 2D 图形部分的内容。
        10.    BufferedImage:
        BufferedImage中包含着width*height个像素点的颜色值,同时BufferedImage中还带有色彩模型(ColorModel)的信息,用于描述像素点的颜色模型,如TYPE_INT_ARGB、TYPE_INT_RGB等。Graphic2D在渲染目标图像时,也需要依照ColorModel来计算图像像素的颜色信息并执行渲染。
        在有些情况下,我们需要对BufferedImage中的每一个像素的颜色值进行计算,并将计算的结果回写到BufferedImage中相应的位置。那么我们是如果获取这些像素信息的呢?又是如果将计算后的颜色值回写的呢?Java 2D中提供了BufferedImage.getRaster()方法,可以直接获取BufferedImage中的光栅信息,再通过WritableRaster.getPixel()方法获取指定位置的像素的颜色值。在对取得的颜色值执行必要的计算后,可通过WritableRaster.setPixel()方法将计算结果回写到光栅的指定位置中。如果图像很大,这样反复的调用getPixel()/setPixel()势必会引起效率问题,WritableRaster为我们提供了getPixels()/setPixels()方法,可以一次获取/回写一组像素颜色值。其使用方式和getPixel()/setPixel()基本一致。
        试想一下,如果我们需要每一种颜色模型(ColorModel)都实现一种处理和计算逻辑,这样会给我们的图形算法带来一些额外的负担,使我们的算法不得不和这些细节打交道,我想这并不是我们希望看到的结果,还有更好的方式可以规避这样的问题吗?答案是肯定的,Java 2D通过下面两条语句来获取标准的颜色值:
        BufferedImage img = loadImageFromFile(filename);
        Raster r = img.getRaster();
        ColorModel cm = img.getColorModel();
        Object data = r.getDataElements(x,y,null);
        int argb = cm.getRGB(data);
        在基于标准ARGB模型的颜色值计算后,可将结果颜色值通过下面两条语句回写到光栅的指定位置。
        Object data = cm.getDataElements(argb,null);
        r.setDataElements(x,y,data);
        下面提供几个典型的代码示例,以供参考。
        1)    在Graphics上绘制BufferedImage的一个简单示例:
        主要功能是将一个图片切割成为4份(2行 * 2列),然后再将切分后的4个子图像进行乱序,换句话说,就是让切割后的子图像不在显示在原有的位置上,最后渲染到Swing的组件上。

     1     public class MyTest extends JPanel {
    2 private int numlocs = 2;
    3 private int numcells = numlocs * numlocs;
    4 private int[] cells;
    5 private BufferedImage bi;
    6 private int w, h, cw, ch;
    7 public MyTest() {
    8 try {
    9 bi = ImageIO.read(new File("D:/desktop.png"));
    10 w = bi.getWidth();
    11 h = bi.getHeight();
    12 } catch (IOException e) {
    13 e.printStackTrace();
    14 }
    15 //将整个图片分隔成为4分,2行 * 2列,这里cx和cy是每个子图片的宽和高
    16 cw = w / numlocs;
    17 ch = h / numlocs;
    18 cells = new int[numcells];
    19 //初始化每个子图片的位置信息
    20 for (int i = 0; i < numcells; i++)
    21 cells[i] = i;
    22 }
    23 void doExchange() {
    24 Random rand = new Random();
    25 int ri;
    26 //将2 * 2 = 4个图片的位置通过随机数的方式打乱。
    27 for (int i = 0; i < numcells; i++) {
    28 while ((ri = rand.nextInt(numlocs)) == i) {
    29 }
    30 int tmp = cells[i];
    31 cells[i] = cells[ri];
    32 cells[ri] = tmp;
    33 }
    34 }
    35 public void paintComponent(Graphics g) {
    36 super.paintComponent(g);
    37 int dx, dy;
    38 //逐个渲染乱序后的每个子图片
    39 for (int x = 0; x < numlocs; x++) {
    40 int sx = x * cw;
    41 for (int y = 0; y < numlocs; y++) {
    42 int sy = y * ch;
    43 int cell = cells[x * numlocs + y];
    44 dx = (cell / numlocs) * cw;
    45 dy = (cell % numlocs) * ch;
    46 //参数说明:
    47 //BufferedImage: 目标绘制图像缓冲区
    48 //dx1,dy1: 绘制目标的左上角x,y坐标
    49 //dx2,dy2: 绘制目标的右下角x,y坐标
    50 //sx1,sy1: 源图像(第一个参数)的左上角x,y坐标
    51 //sx2,sy2: 源图像(第一个参数)的右下角x,y坐标
    52 g.drawImage(bi, dx, dy, dx + cw, dy + ch, sx, sy, sx + cw, sy + ch, null);
    53 }
    54 }
    55 }
    56 public static void main(String[] args) {
    57 JFrame frame = new JFrame();
    58 frame.setTitle("BufferedImage");
    59 frame.setSize(1000, 600);
    60 frame.addWindowListener(new WindowAdapter() {
    61 public void windowClosing(WindowEvent e) {
    62 System.exit(0);
    63 }
    64 });
    65 Container contentPane = frame.getContentPane();
    66 MyTest p = new MyTest();
    67 contentPane.add(p);
    68 p.doExchange();
    69 frame.show();
    70 }
    71 }

        2)    通过多种方式在(利用Graphics2D.drawImage()的不同重载方法)目标Graphics上绘制多种处理后的图像,如缩放、ConvolveOp的锐化,RescaleOp的改变亮度等。

     1     public class MyTest extends JPanel {
    2 private BufferedImage bi;
    3 private static int ALL_WIDTH = 900;
    4 private static int ALL_HEIGHT = 600;
    5 private int w, h;
    6 public static final float[] SHARPEN3x3 = { 0.f, -1.f, 0.f, -1.f, 5.f, -1.f, 0.f, -1.f, 0.f };
    7 //可以将1,1坐标的锐化data替换为这里的模糊data,该观察效果。
    8 public static final float[] BLUR3x3 = { 0.1f, 0.1f, 0.1f, 0.1f, 0.2f, 0.1f, 0.1f, 0.1f, 0.1f };
    9 public MyTest() {
    10 try {
    11 // 这里的样例图片是截取的桌面背景,width和height都是比较大的,
    12 // 同时为了简化代码突出重点,因此这里只是给出了固定的宽和高
    13 bi = ImageIO.read(new File("D:/desktop.png"));
    14 // 由于我们的整个JFrame将同时显示六种(2行*3列)不同效果的目标
    15 // 子图像,因此这里需要针对原图的宽和高作特殊处理,并取出子图像
    16 bi = bi.getSubimage(0, 0, ALL_WIDTH / 3, ALL_HEIGHT / 2);
    17 w = bi.getWidth();
    18 h = bi.getHeight();
    19 if (bi.getType() != BufferedImage.TYPE_INT_RGB) {
    20 BufferedImage bi2 = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
    21 bi2.getGraphics().drawImage(bi, 0, 0, null);
    22 bi = bi2;
    23 }
    24 } catch (IOException e) {
    25 }
    26 }
    27 public void paintComponent(Graphics g) {
    28 super.paintComponent(g);
    29 Graphics2D g2 = (Graphics2D) g;
    30
    31 int offsetx = 0;
    32 int offsety = 0;
    33 // 1. 在0,0的位置显示原始图像
    34 g.drawImage(bi, offsetx, offsety, null);
    35 offsetx += bi.getWidth();
    36 // 2. 在0,1的位置显示通过原始坐标进行缩放的图像,该行代码将原始图像的部分图像放大一倍
    37 g.drawImage(bi, offsetx, offsety, offsetx + w, offsety + h, 0, 0, w / 2, h / 2, null);
    38 offsetx += bi.getWidth();
    39 // 3. 在0,2的位置显示通过AffineTransform进行缩放后的图像
    40 AffineTransform at = AffineTransform.getTranslateInstance(offsetx, offsety);
    41 at.scale(0.7, 0.7);
    42 g2.drawImage(bi, at, null);
    43 offsetx = 0;
    44 offsety += bi.getHeight();
    45 // 4. 在1,0的位置显示通过AffineTransformOp处理后的图像.
    46 // AffineTransformOp是通过TYPE_BICUBIC(质量最高的)提示将原始图像进行缩放。
    47 AffineTransform at2 = AffineTransform.getScaleInstance(1.5, 1.5);
    48 AffineTransformOp aop = new AffineTransformOp(at2, AffineTransformOp.TYPE_BICUBIC);
    49 BufferedImage bi2 = new BufferedImage(bi.getWidth()
    50 ,bi.getHeight(),BufferedImage.TYPE_INT_RGB);
    51 //这里主要是为了确保每个图像都显示在各自的单元格内,因此用一个临时的
    52 //BufferedImage对象替换原有对象显示。事实上,是可以通过以下语句直接渲染的。
    53 //g2.drawImage(bi,aop,offsetx,offsety);但是这样的渲染结果将会使width放大1.5倍。
    54 aop.filter(bi, bi2);
    55 g2.drawImage(bi2, offsetx, offsety,null);
    56 offsetx += bi.getWidth();
    57 // 5. 在1,1的位置显示锐化后原图
    58 float[] data = SHARPEN3x3;
    59 ConvolveOp cop = new ConvolveOp(new Kernel(3, 3, data), ConvolveOp.EDGE_NO_OP, null);
    60 g2.drawImage(bi, cop, offsetx, offsety);
    61 offsetx += bi.getWidth();
    62 // 6. 在1,2的位置显示通过RescaleOp图像处理器改变原图的灰度。
    63 RescaleOp rop = new RescaleOp(1.1f, 20.0f, null);
    64 g2.drawImage(bi, rop, offsetx, offsety);
    65 }
    66 public static void main(String[] args) {
    67 JFrame frame = new JFrame();
    68 frame.setTitle("BufferedImage");
    69 frame.setSize(ALL_WIDTH, ALL_HEIGHT);
    70 frame.addWindowListener(new WindowAdapter() {
    71 public void windowClosing(WindowEvent e) {
    72 System.exit(0);
    73 }
    74 });
    75 Container contentPane = frame.getContentPane();
    76 MyTest p = new MyTest();
    77 contentPane.add(p);
    78 frame.show();
    79 }
    80 }

        3)    在介绍ColorModel之前,我们需要先了解在Java 2D 中另外一组比较重要的图形工具对象--图形环境(GraphicsEnvironment)、图形设备(GraphicsDevice)和图形配置(GraphicsConfiguration)。通过下面这个简单的示例代码,可以非常清楚的看出他们之间的关系以及各自的作用。

     1     public class MyTest {
    2 public static void main(String[] args) {
    3 //1. 获取本地的图形环境
    4 GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
    5 //2. 获取所有的屏幕设备
    6 GraphicsDevice[] gs = ge.getScreenDevices();
    7 //3. 获取每个屏幕设备的配置对象
    8 for (int j = 0; j < gs.length; j++) {
    9 GraphicsDevice gd = gs[j];
    10 System.out.println("Device " + j + ": " + gd);
    11 GraphicsConfiguration[] gc = gd.getConfigurations();
    12 for (int i = 0; i < gc.length; i++) {
    13 System.out.println(" Configuration " + i + ": " + gc[i]);
    14 System.out.println(" Bounds: " + gc[i].getBounds());
    15 }
    16 }
    17 }
    18 }
    19 /* 输出结果如下:
    20 Device 0: Win32GraphicsDevice[screen=0]
    21 Configuration 0: sun.awt.Win32GraphicsConfig@1b8d6f7[dev=Win32GraphicsDevice[screen=0],pixfmt=0]
    22 Bounds: java.awt.Rectangle[x=0,y=0,width=1280,height=800]
    23 */

        4)    通过图形设备工具类获取和屏幕相关的Metrics信息。

     1     public class MyTest {
    2 public static void main(String[] args) {
    3 GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
    4 GraphicsDevice gs = ge.getDefaultScreenDevice();
    5 DisplayMode[] dmodes = gs.getDisplayModes();
    6 for (int i = 0; i < dmodes.length; i++) {
    7 int w = dmodes[i].getWidth();
    8 int h = dmodes[i].getHeight();
    9 int depth = dmodes[i].getBitDepth();
    10 int refreshRate = dmodes[i].getRefreshRate();
    11 System.out.printf("ScreenWidth = %d\t ScreenHeight = %d\t " +
    12 "BitDepth = %d\t RefreshRate = %d\n",w,h,depth,refreshRate);
    13 }
    14 DisplayMode currentDMode = gs.getDisplayMode();
    15 int w = currentDMode.getWidth();
    16 int h = currentDMode.getHeight();
    17 int depth = currentDMode.getBitDepth();
    18 int refreshRate = currentDMode.getRefreshRate();
    19 System.out.println("The current Display Modes is ");
    20 System.out.printf("ScreenWidth = %d\t ScreenHeight = %d\t " +
    21 "BitDepth = %d\t RefreshRate = %d\n",w,h,depth,refreshRate);
    22 }
    23 }

        5)    通过图形配置工具类创建和当前图形设备类型兼容的BufferedImage。
        这里需要说明一下,每个图形设备都会有一组和当前设备相关的图形配置信息,如分辨率、位深度和色彩类型等。不同的设备之间其图形配置可能存在较大的差异,如屏幕和打印机。那么创建和设备兼容的BufferedImage的目的和应用是什么呢?目的很简单就是为了提高渲染效率。比如当前的BufferedImage对象需要被渲染到JPanel上面,如果该BufferedImage对象的图形配置信息和显示JPanel的屏幕的配置信息相一致,那么在Graphics渲染时,就可以避免因大量的数据转义而带来的额外开销。这种技巧一个非常典型的应用就是Swing的双缓冲技术。由于本篇并不是介绍Swing的专题,而双缓冲又是一个非常通用的技术,这里就不再给出更多的解释了。
        下面的示例代码将给出一个用于创建与设备兼容的BufferedImage对象的工具类。

     1     class CompatibleImageUtil {
    2 private static GraphicsConfiguration gc;
    3 public static GraphicsConfiguration getConfiguration() {
    4 if (gc == null) {
    5 // 1. 获取本地当前正在使用的图形环境
    6 GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
    7 // 2. 获取当前环境正在使用的图形设备
    8 GraphicsDevice gs = ge.getDefaultScreenDevice();
    9 // 3. 获取当前设备正在使用的图形配置。
    10 gc = gs.getDefaultConfiguration();
    11 }
    12 return gc;
    13 }
    14 //基于参数srcImage的宽度、高度和透明度参数来创建一个和设备兼容的BufferedImage对象
    15 public static BufferedImage createCompatibleImage(BufferedImage srcImage) {
    16 return createCompatibleImage(srcImage, srcImage.getWidth(), srcImage.getHeight());
    17 }
    18 public static BufferedImage createCompatibleImage(BufferedImage srcImage, int width, int height) {
    19 return getConfiguration().createCompatibleImage(width, height, srcImage.getTransparency());
    20 }
    21 public static BufferedImage createCompatibleImage(int width, int height) {
    22 return getConfiguration().createCompatibleImage(width, height);
    23 }
    24 public static BufferedImage createCompatibleTranslucentImage(int width, int height) {
    25 return getConfiguration().createCompatibleImage(width, height, Transparency.TRANSLUCENT);
    26 }
    27 //加载原图像,在基于加载后源图像的宽度、高度和透明度创建一个和设备兼容的BufferedImage对象
    28 public static BufferedImage loadCompatibleImage(File filename) throws IOException {
    29 BufferedImage image = ImageIO.read(filename);
    30 return toCompatibleImage(image);
    31 }
    32 public static BufferedImage toCompatibleImage(BufferedImage image) {
    33 GraphicsConfiguration gc = getConfiguration();
    34 //如果源图像的ColorModel和当前设备的ColorModel兼容,则直接返回
    35 if (image.getColorModel().equals(gc.getColorModel()))
    36 return image;
    37 //基于源图像创建和设备兼容的目标图像
    38 BufferedImage compatibleImage = gc.createCompatibleImage(image.getWidth()
    39 , image.getHeight(),image.getTransparency());
    40 //再将源图像绘制到目标图像后返回
    41 Graphics g = compatibleImage.getGraphics();
    42 g.drawImage(image, 0, 0, null);
    43 g.dispose();
    44 return compatibleImage;
    45 }
    46 }

        6)    通过直接操作BufferedImage对象内部的光栅对象来处理图像的每一个像素。

     1     public class MyTest extends JPanel {
    2 private BufferedImage srcImage;
    3 private BufferedImage flippedImage;
    4 MyTest() {
    5 try {
    6 Image image = ImageIO.read(new File("D:/desktop.png"));
    7 // 由于该测试图片是桌面的快照,因此比较大,这里为了显示方便需要截取
    8 srcImage = new BufferedImage(300,300, BufferedImage.TYPE_INT_ARGB);
    9 Graphics g = srcImage.getGraphics();
    10 g.drawImage(image, 0, 0, 300,300,0,0,300,300,null);
    11 flippedImage = new BufferedImage(srcImage.getWidth(),
    12 srcImage.getHeight(), srcImage.getType());
    13 //直接获取源图像和目的图像的光栅数据
    14 DataBuffer dbSrc = srcImage.getRaster().getDataBuffer();
    15 DataBuffer dbFlipped = flippedImage.getRaster().getDataBuffer();
    16 //直接操作光栅数据,这里将源图像的数据数组反向写入目标图像。
    17 //从而达到翻转的效果
    18 for (int i = dbSrc.getSize() - 1, j = 0; i >= 0; --i, j++) {
    19 dbFlipped.setElem(j, dbSrc.getElem(i));
    20 }
    21 } catch (IOException e) {
    22 }
    23 }
    24 public void paintComponent(Graphics g) {
    25 super.paintComponent(g);
    26 g.drawImage(srcImage, 0, 0, null);
    27 g.drawImage(flippedImage,300,0,null);
    28 }
    29 public static void main(String[] args) throws IOException {
    30 JFrame frame = new JFrame();
    31 frame.setTitle("Write with Raster");
    32 frame.setSize(600, 300);
    33 frame.addWindowListener(new WindowAdapter() {
    34 public void windowClosing(WindowEvent e) {
    35 System.exit(0);
    36 }
    37 });
    38 Container contentPane = frame.getContentPane();
    39 contentPane.add(new MyTest());
    40 frame.show();
    41 }
    42 }

        7)    通过直接操作像素颜色数组的方式更新BufferedImage的光栅数据:

     1     public class MyTest extends JPanel {
    2 public static void main(String[] args) throws IOException {
    3 JFrame frame = new JFrame();
    4 frame.setTitle("");
    5 frame.setSize(600, 300);
    6 frame.addWindowListener(new WindowAdapter() {
    7 public void windowClosing(WindowEvent e) {
    8 System.exit(0);
    9 }
    10 });
    11 Container contentPane = frame.getContentPane();
    12 contentPane.add(new MyTest());
    13 frame.show();
    14 }
    15
    16 private BufferedImage originalImage;
    17 private int w;
    18 private int h;
    19 private WritableRaster raster;
    20
    21 MyTest() {
    22 Image image = null;
    23 try {
    24 image = ImageIO.read(new File("D:/desktop.png"));
    25 } catch (IOException e) {
    26 e.printStackTrace();
    27 }
    28 originalImage = new BufferedImage(image.getWidth(null),image.getHeight(null),
    29 BufferedImage.TYPE_INT_RGB);
    30 Graphics g = originalImage.getGraphics();
    31 g.drawImage(image,0,0,null);
    32 raster = originalImage.getRaster();
    33 w = originalImage.getWidth();
    34 h = originalImage.getHeight();
    35 int[] iArray = null;
    36 //getPixels返回的数组是BufferedImage内部真实数据的copy
    37 int[] array = raster.getPixels(0, 0, w, h, iArray);
    38 for (int i = 0; i < array.length; ++i) {
    39 array[i] = array[i] - 10;
    40 }
    41 raster.setPixels(0, 0, w, h, array);
    42 }
    43
    44 public void paintComponent(Graphics g) {
    45 super.paintComponent(g);
    46 g.drawImage(originalImage, 0, 0, null);
    47 }
    48 }

        11.    图像处理:
        如果你有一个图像并且想改变他的外观,该怎么办呢?这是你将需要访问该图像的每一个像素,并用其他的像素来取代这些像素。Java 2D中提供了BufferedImageOp的接口,实现了该接口的类可以对图像进行变换操作。具体使用方式可以参照下面示例代码:
        BufferedImageOp op = getBufferedImageOperation();
        BufferedImage fileteredImage = new BufferedImage(img.getWidth(),img.getHeight(),img.getType());
        op.filter(img,filteredImage);
        在Java 2D中为我们提供了5个BufferedImageOp接口的实现类,他们分别是AffineTransformOp、RescaleOp、LookupOp、ColorConvertOp和ConvolveOp。下面的示例代码将给出三种比较常用的图像处理实现类AffineTransformOp、Rescale和ConvolveOp的使用方式。

     1     public class MyTest extends JPanel {
    2 private BufferedImage image;
    3 private static int WINDOW_WIDTH = 600;
    4 private static int WINDOW_HEIGHT = 600;
    5 static AffineTransform mirrorTransform;
    6 static {
    7 mirrorTransform = AffineTransform.getTranslateInstance(WINDOW_WIDTH/4,0);
    8 // 水平翻转
    9 mirrorTransform.scale(-1.0, 1.0);
    10 }
    11 //初始化所有的BufferedImageOp
    12 static BufferedImageOp[] filters = new BufferedImageOp[] {
    13 // 1) 显示源图像作为对比
    14 null,
    15 // 2) 图像的反色显示,这里需要将BufferedImage中每个点的像素都
    16 // 乘以-1,在加255。
    17 new RescaleOp(-1.0f, 255f, null),
    18 // 3) 将亮度提高1.25倍
    19 new RescaleOp(1.25f, 0, null),
    20 // 4) 模糊该图像,这里的图像过滤主要是和Kernel的数据值相关。
    21 new ConvolveOp(new Kernel(3, 3, new float[] { 1/9f, 1/9f, 1/9f, 1/9f, 1/9f, 1/9f,
    22 1/9f, 1/9f, 1/9f })),
    23 // 5) 锐化该图像。
    24 new ConvolveOp(new Kernel(3, 3, new float[] { 0.0f, -0.75f, 0.0f, -0.75f, 4.0f, -0.75f, 0.0f,
    25 -0.75f, 0.0f })),
    26 // 6) 边缘检测。
    27 new ConvolveOp(new Kernel(3, 3, new float[] { 0.0f, -0.75f, 0.0f, -0.75f, 3.0f, -0.75f, 0.0f,
    28 -0.75f, 0.0f })),
    29 // 7) 通过mirrorTransform.scale(-1.0, 1.0)的技巧翻转图片(水平翻转)
    30 new AffineTransformOp(mirrorTransform, AffineTransformOp.TYPE_BILINEAR),
    31 // 8) 通过rotate变换方式翻转,这里的180度翻转只是一个特例,为了便于演示,
    32 // 事实上可以翻转任意角度, AnchorPoint表示翻转是的作用点(圆心),该坐标是
    33 // 相对于该图像的左上角的偏移值。
    34 new AffineTransformOp(AffineTransform.getRotateInstance(Math.PI,WINDOW_WIDTH/8,WINDOW_HEIGHT/4),
    35 AffineTransformOp.TYPE_NEAREST_NEIGHBOR)
    36 };
    37 public MyTest() {
    38 try {
    39 image = ImageIO.read(new File("D:/desktop.png"));
    40 //这里做一个源图像截取,便于后面的规范化显示。
    41 image = image.getSubimage(0, 0,WINDOW_WIDTH/4, WINDOW_HEIGHT/2);
    42 } catch (IOException e) {
    43 }
    44 }
    45 @Override
    46 public Dimension getPreferredSize() {
    47 return new Dimension(WINDOW_WIDTH,WINDOW_HEIGHT);
    48 }
    49 public void paintComponent(Graphics g) {
    50 super.paintComponent(g);
    51 BufferedImage bimage = new BufferedImage(image.getWidth(), image.getHeight()
    52 , BufferedImage.TYPE_INT_RGB);
    53 Graphics2D ig = bimage.createGraphics();
    54 ig.drawImage(image, 0, 0, null);
    55 for (int i = 0; i < filters.length; i++) {
    56 // 如果filters[i]的值是null,需要copy源图像,否则使用图像处理器处理图像。
    57 if (filters[i] == null)
    58 g.drawImage(bimage, 0, 0, null);
    59 else
    60 g.drawImage(filters[i].filter(bimage, null), 0, 0, null);
    61 //平行移动
    62 g.translate(WINDOW_WIDTH / 4, 0);
    63 //向下移动,同时将x的坐标移回0的位置,由于translate和Graphics中
    64 //之前的坐标变换是组合的,所以这里只能用这种方法偏移,而不能直接置零
    65 if ((i + 1) % 4 == 0 && i != 0)
    66 g.translate(-WINDOW_WIDTH, WINDOW_HEIGHT / 2);
    67 }
    68 ig.dispose();
    69 }
    70 public static void main(String[] args) {
    71 JFrame frame = new JFrame();
    72 frame.setTitle("BufferedImageOp");
    73 frame.addWindowListener(new WindowAdapter() {
    74 public void windowClosing(WindowEvent e) {
    75 System.exit(0);
    76 }
    77 });
    78 frame.setContentPane(new MyTest());
    79 frame.pack();
    80 frame.setVisible(true);
    81 }
    82 }
  • 相关阅读:
    hadoop-处理小文件
    hadoop 文件合并
    hadoop multipleoutputs
    超酷的 Vim 搜索技巧
    linux中DHCP服务配置文件/etc/dhcpd.conf详细说明
    cobbler启动问题
    MYSQL 5.5.32的单机多实例部署
    自动化运维之Cobbler自动化部署安装操作系统
    运维自动化之Cobbler系统安装使用详解[good]
    Cobbler自动部署主机系统
  • 原文地址:https://www.cnblogs.com/orangeform/p/2185549.html
Copyright © 2020-2023  润新知