/* * ImageManipulation.java * * CMPT 460, Assignment #3 * Andrey Mirtchovski * * Description: * This applet implements three (well, actually four) different algorithms for halftoning. * * The user interface will not be explained here, since it is assumed that it's trivial * to use. * * The algorithms implemented are: * - simple halftoning (identical to the one posted on the course web page) method: halftone(); * - floyd-steinberg -- the induced for each pixel through the halftoning process are propagated to the neighbouring pixels below and forward to the current one. method: f_s(); - dot diffusion (Knuth's algorithm) -- this algorithm is implemented with the two different matrices given in notes (M1 and M2) where M2 is assumed to be the better one. The algorithm consists of using a mapped matrix to lookup the neighbours of the current pixel processed, on which to propagate the error. method: d_d(); * * The code is well documented and very little in the implementation has been changed from * class notes, so it should be fairly straightforward to follow the code. * Status: The program is fully operational and is believed to display correct halftoned * pictures. * * Limitations: The program was not optimized for the dot diffusion algorithm and it is rather * slow in graphing the cumulative errors. Unfortunately this could be fixed by submission time. * * Tested on: * WinNT, Win95, Win98, Sun5.7, Sun5.8(Solaris), IRIX6.5, Netscape 4.x, IE5, FreeBSD... * * */ import java.awt.*; import java.awt.event.*; import java.applet.*; import java.net.URL; import java.awt.image.*; import java.util.*; public class ImageManipulation extends Applet { // IMPORTANT: Source code between BEGIN/END comment pair will be regenerated // every time the form is saved. All manual changes will be overwritten. // BEGIN GENERATED CODE // member declarations java.awt.Checkbox checkbox1 = new java.awt.Checkbox(); java.awt.Choice choice1 = new java.awt.Choice(); java.awt.Choice choice2 = new java.awt.Choice(); java.awt.Label label1 = new java.awt.Label(); java.awt.Label label2 = new java.awt.Label(); java.awt.Label label3 = new java.awt.Label(); java.awt.Button button1 = new java.awt.Button(); java.awt.TextField textField1 = new java.awt.TextField(); java.awt.TextField textField2 = new java.awt.TextField(); java.awt.TextField textField3 = new java.awt.TextField(); javax.swing.JSlider jSlider1 = new javax.swing.JSlider(); javax.swing.JSlider jSlider2 = new javax.swing.JSlider(); javax.swing.JSlider jSlider3 = new javax.swing.JSlider(); java.awt.Label label4 = new java.awt.Label(); java.awt.Label label5 = new java.awt.Label(); java.awt.Label label6 = new java.awt.Label(); java.awt.Label label7 = new java.awt.Label(); java.awt.Label label8 = new java.awt.Label(); java.awt.Label label9 = new java.awt.Label(); // END GENERATED CODE Image image, image_out; int imw, imh, pixel, pixels[]; ColorModel defaultRGB = ColorModel.getRGBdefault(); int pix[]; // global storage for pixels -- improves speed int dx = 520, dy = 100; // position we draw the graph image on double errRed = 0, errGreen = 0, errBlue = 0; // cumulative errors for different colors /* dot diffusion matrices */ int M2[] = { /* M2 -- from class notes */ 34,48,40,32,29,15,23,31, 42,58,56,53,21, 5, 7,10, 50,62,61,45,13, 1, 2,18, 38,46,54,37,25,17, 9,26, 28,14,22,30,35,49,41,33, 20, 4, 6,11,43,59,57,52, 12, 0, 3,19,51,63,60,44, 24,16, 8,27,39,47,55,36}; int M1[] = { /* M1 -- from class notes */ 0,32, 8,40,2,34,10,42, 48,16,56,24,50,18,58,26, 12,44,4,36,14,46,6,38, 60,28,52,20,62,30,54,22, 3,35,11,43,1,33,9,41, 51,19,59,27,49,17,57,25, 15,47,7,39,13,45,5,37, 63,31,55,23,61,29,53,21,}; int dd[]; /* used as 'the current matrix' in d_d() */ boolean isStandalone = false; public ImageManipulation() { } // Retrieve the value of an applet parameter public String getParameter(String key, String def) { return isStandalone ? System.getProperty(key, def) : (getParameter(key) != null ? getParameter(key) : def); } // Get info on the applet parameters public String[][] getParameterInfo() { return null; } // Get applet information public String getAppletInfo() { return "Applet Information"; } // Initialize the applet public void init() { try { initComponents(); // add algorithms choice1.add("Floyd-Steinberg"); choice1.add("Dot Diffusion(M1)"); choice1.add("Dot Diffusion(M2)"); choice1.add("Simple Halftone"); // add pictures // nb: http://www.cs.usask.ca/undergrads/aam396/java/im.html // has an example of more pictures choice2.add("New Zealand"); choice2.add("Ireland"); choice2.add("The Nut"); choice2.add("Me 1"); choice2.add("Me 2"); choice2.add("Me 3"); choice2.add("Me 4"); choice2.add("Me 5"); choice2.add("Me 6"); choice2.add("Me 7"); choice2.add("Me 8"); choice2.add("Me 10"); choice2.add("Me 11"); choice2.add("snow"); choice2.add("b"); // default picture displayed URL url = getClass().getResource("new_zealand.gif"); try { image = createImage((ImageProducer)url.getContent()); } catch(Exception e) { e.printStackTrace(); } } catch (Exception e) { e.printStackTrace(); } } public void initComponents() throws Exception { // IMPORTANT: Source code between BEGIN/END comment pair will be regenerated // every time the form is saved. All manual changes will be overwritten. // BEGIN GENERATED CODE // the following code sets the frame's initial state checkbox1.setVisible(true); checkbox1.setLabel("graph error"); checkbox1.setLocation(new java.awt.Point(370, 300)); checkbox1.setSize(new java.awt.Dimension(90, 23)); choice1.setVisible(true); choice1.setLocation(new java.awt.Point(370, 60)); choice1.setSize(new java.awt.Dimension(130, 21)); choice2.setVisible(true); choice2.setLocation(new java.awt.Point(40, 30)); choice2.setSize(new java.awt.Dimension(120, 21)); label1.setVisible(true); label1.setLocation(new java.awt.Point(370, 100)); label1.setText("red:"); label1.setSize(new java.awt.Dimension(50, 20)); label2.setVisible(true); label2.setLocation(new java.awt.Point(370, 160)); label2.setText("green:"); label2.setSize(new java.awt.Dimension(50, 20)); label3.setVisible(true); label3.setLocation(new java.awt.Point(370, 210)); label3.setText("blue:"); label3.setSize(new java.awt.Dimension(50, 20)); button1.setVisible(true); button1.setLabel("Go!"); button1.setLocation(new java.awt.Point(370, 270)); button1.setSize(new java.awt.Dimension(130, 30)); textField1.setLocation(new java.awt.Point(460, 100)); textField1.setVisible(true); textField1.setText("125"); textField1.setSize(new java.awt.Dimension(40, 23)); textField2.setLocation(new java.awt.Point(460, 160)); textField2.setVisible(true); textField2.setText("125"); textField2.setSize(new java.awt.Dimension(40, 23)); textField3.setLocation(new java.awt.Point(460, 210)); textField3.setVisible(true); textField3.setText("125"); textField3.setSize(new java.awt.Dimension(40, 20)); jSlider1.setSize(new java.awt.Dimension(130, 15)); jSlider1.setLocation(new java.awt.Point(370, 130)); jSlider1.setVisible(true); jSlider1.setMajorTickSpacing(10); jSlider1.setMaximum(255); jSlider1.setValue(125); jSlider2.setSize(new java.awt.Dimension(130, 15)); jSlider2.setLocation(new java.awt.Point(370, 190)); jSlider2.setVisible(true); jSlider2.setMajorTickSpacing(10); jSlider2.setMaximum(255); jSlider2.setValue(125); jSlider3.setSize(new java.awt.Dimension(130, 15)); jSlider3.setLocation(new java.awt.Point(370, 240)); jSlider3.setVisible(true); jSlider3.setMajorTickSpacing(10); jSlider3.setMaximum(255); jSlider3.setValue(125); label4.setVisible(true); label4.setLocation(new java.awt.Point(40, 10)); label4.setText("Select input image:"); label4.setSize(new java.awt.Dimension(120, 23)); label5.setVisible(true); label5.setLocation(new java.awt.Point(370, 30)); label5.setText("Select algorithm:"); label5.setSize(new java.awt.Dimension(104, 23)); label6.setVisible(true); label6.setLocation(new java.awt.Point(370, 80)); label6.setText("Modify color treshholds:"); label6.setSize(new java.awt.Dimension(130, 20)); label7.setVisible(true); label7.setAlignment(Label.RIGHT); label7.setLocation(new java.awt.Point(460, 300)); label7.setSize(new java.awt.Dimension(40, 20)); label8.setVisible(true); label8.setLocation(new java.awt.Point(370, 330)); label8.setSize(new java.awt.Dimension(100, 20)); label9.setVisible(true); label9.setAlignment(Label.RIGHT); label9.setLocation(new java.awt.Point(400, 360)); label9.setSize(new java.awt.Dimension(100, 20)); setLocation(new java.awt.Point(0, 0)); setSize(new java.awt.Dimension(904, 416)); setBackground(new java.awt.Color(128, 128, 128)); setLayout(null); add(checkbox1); add(choice1); add(choice2); add(label1); add(label2); add(label3); add(button1); add(textField1); add(textField2); add(textField3); add(jSlider1); add(jSlider2); add(jSlider3); add(label4); add(label5); add(label6); add(label7); add(label8); add(label9); choice2.addItemListener(new java.awt.event.ItemListener() { public void itemStateChanged(java.awt.event.ItemEvent e) { choice2ItemStateChanged(e); } }); button1.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent e) { button1ActionPerformed(e); } }); textField1.addKeyListener(new java.awt.event.KeyAdapter() { public void keyTyped(java.awt.event.KeyEvent e) { textField1KeyTyped(e); } }); textField1.addFocusListener(new java.awt.event.FocusAdapter() { public void focusLost(java.awt.event.FocusEvent e) { textField1FocusLost(e); } }); textField1.addTextListener(new java.awt.event.TextListener() { public void textValueChanged(java.awt.event.TextEvent e) { textField1TextValueChanged(e); } }); textField2.addKeyListener(new java.awt.event.KeyAdapter() { public void keyTyped(java.awt.event.KeyEvent e) { textField2KeyTyped(e); } }); textField2.addFocusListener(new java.awt.event.FocusAdapter() { public void focusLost(java.awt.event.FocusEvent e) { textField2FocusLost(e); } }); textField3.addKeyListener(new java.awt.event.KeyAdapter() { public void keyTyped(java.awt.event.KeyEvent e) { textField3KeyTyped(e); } }); textField3.addFocusListener(new java.awt.event.FocusAdapter() { public void focusLost(java.awt.event.FocusEvent e) { textField3FocusLost(e); } }); jSlider1.addChangeListener(new javax.swing.event.ChangeListener() { public void stateChanged(javax.swing.event.ChangeEvent e) { jSlider1StateChanged(e); } }); jSlider2.addChangeListener(new javax.swing.event.ChangeListener() { public void stateChanged(javax.swing.event.ChangeEvent e) { jSlider2StateChanged(e); } }); jSlider3.addChangeListener(new javax.swing.event.ChangeListener() { public void stateChanged(javax.swing.event.ChangeEvent e) { jSlider3StateChanged(e); } }); // END GENERATED CODE } public void textField1TextValueChanged(java.awt.event.TextEvent e) { } // used when slider is changed -- display the new value in the corresponding // text field public void jSlider1StateChanged(javax.swing.event.ChangeEvent e) { textField1.setText(""+jSlider1.getValue()); } // set the text field data onto the slider public void textField1FocusLost(java.awt.event.FocusEvent e) { jSlider1.setValue(new Integer(textField1.getText()).intValue()); } public void textField1KeyTyped(java.awt.event.KeyEvent e) { if(e.getKeyChar() == KeyEvent.VK_ENTER) jSlider1.setValue(new Integer(textField1.getText()).intValue()); } // same as above public void textField2FocusLost(java.awt.event.FocusEvent e) { jSlider2.setValue(new Integer(textField2.getText()).intValue()); } public void textField2KeyTyped(java.awt.event.KeyEvent e) { if(e.getKeyChar() == KeyEvent.VK_ENTER) jSlider2.setValue(new Integer(textField3.getText()).intValue()); } public void jSlider2StateChanged(javax.swing.event.ChangeEvent e) { textField2.setText(""+jSlider2.getValue()); } public void textField3FocusLost(java.awt.event.FocusEvent e) { jSlider3.setValue(new Integer(textField3.getText()).intValue()); } public void textField3KeyTyped(java.awt.event.KeyEvent e) { if(e.getKeyChar() == KeyEvent.VK_ENTER) jSlider3.setValue(new Integer(textField3.getText()).intValue()); } public void jSlider3StateChanged(javax.swing.event.ChangeEvent e) { textField3.setText(""+jSlider3.getValue()); } // repaint the graphics, update the swing components that // are not normally updated public void paint(Graphics g) { jSlider1.updateUI(); jSlider2.updateUI(); jSlider3.updateUI(); g.drawImage(image, 10,100, this); if(image_out != null) { g.drawImage(image_out, dx, dy, this); } } // leftover from an earlier implementation with animation public void repaint(Graphics g) { paint(g); } // display a new image when the user chooses one public void choice2ItemStateChanged(java.awt.event.ItemEvent e) { int index = choice2.getSelectedIndex(); URL url = null; switch (index) { case 0: url = getClass().getResource("new_zealand.gif"); break; case 1: url = getClass().getResource("ireland.gif"); break; case 2: url = getClass().getResource("the_nut.jpg"); break; case 3: url = getClass().getResource("me1.jpg"); break; case 4: url = getClass().getResource("me2.jpg"); break; case 5: url = getClass().getResource("me3.jpg"); break; case 6: url = getClass().getResource("me4.jpg"); break; case 7: url = getClass().getResource("me5.jpg"); break; case 8: url = getClass().getResource("me6.jpg"); break; case 9: url = getClass().getResource("me7.jpg"); break; case 10: url = getClass().getResource("me8.jpg"); break; case 11: url = getClass().getResource("me10.gif"); break; case 12: url = getClass().getResource("me11.gif"); break; case 13: url = getClass().getResource("snow.jpg"); break; case 14: url = getClass().getResource("b.jpg"); break; default: } try { // fetch the selected image image = createImage((ImageProducer)url.getContent()); } catch(Exception e1) {e1.printStackTrace();} // clean up the previously rendered image image_out = null; repaint(); } // this routine is responsible for setting up and calling the different // implementation algorithms. it extracts data from the file and // depending on whether the checkbox is clicked displays either the // cumulative error graphs for this image or the halftoned result obtained // with current threshold values. public void button1ActionPerformed(java.awt.event.ActionEvent e) { int index = choice1.getSelectedIndex(); URL url = null; // get image beforehand and save time within actual routines // this is used to optimize speed -- calling PixelGrabber is a // cpu-cycle killer. imw = image.getWidth(this); imh = image.getWidth(this); pix = new int[imw*imh]; // define the memory buffer for processing try { PixelGrabber pg = new PixelGrabber(image, 0, 0, imw, imh, pix, 0, imw); // extract the data from the file pg.grabPixels(); }catch(InterruptedException e1) { e1.printStackTrace(); } // min[] and max[] are used to display the minimum and maximum cumulative // error when drawing the graph for a particular image int min[], max[]; min = new int[2]; min[0] = 0; // threshold of minimum error; at this threshold value the // minimum cumulative error was found.. i.e. this is closest // to the real image min[1] = 2000000; // *the* minimum error, averaged // the actual minimum error. we start with a very high value // and decrement accordingly max = new int[2]; max[0] = 0; // threshold of maximum error max[1] = 0; // max error if(checkbox1.getState()) { // checkbox is on, cycle through all the thresholds and calculate // corresponding cumulative errors... draw on screen (g) and save in // an Image for such things as resizing, minimizing, etc... image_out = createImage(imw, imh); Graphics g = this.getGraphics(); // used for real-time display Graphics g2 = image_out.getGraphics(); // used for repainting g.setColor(Color.lightGray); g.fillRect(dx, dy, imw, imh); g.setColor(Color.black); g2.setColor(Color.lightGray); g2.fillRect(0, 0, imw, imh); g2.setColor(Color.black); int i; int err=0; for(i = 0; i < 256; i++) { // for all threshold values find out the errors // note that each routine returns only the cumulative error // but sets the global errorRed, errorGreen, errorBlue for // individual color errors. switch (index) { case 0: err = (int)Math.round(f_s(i, i, i))/(imw*imh); break; case 1: dd = M1; err = (int)Math.round(d_d(i, i, i))/(imw*imh); break; case 2: dd = M2; err = (int)Math.round(d_d(i, i, i))/(imw*imh); break; case 3: err = (int)Math.round(halftone(i, i, i))/(imw*imh); break; default: } // after routines have been called, we can calculate // the cumulative errors. normalize by the size*width of image // just to look nice int errR = (int)Math.round(errRed)/(imw*imh); int errG = (int)Math.round(errGreen)/(imw*imh); int errB = (int)Math.round(errBlue)/(imw*imh); // calculate best and worst thresholds max[0] = err > max[1] ? i : max[0]; max[1] = err > max[1] ? err : max[1]; min[0] = err < min[1] ? i : min[0]; min[1] = err < min[1] ? err : min[1]; // draw graphics :) g.setColor(Color.black); g.draw3DRect(dx + i,dy + 300-err, 1, 1, true); g.setColor(Color.red); g.draw3DRect(dx + i,dy + 300-errR, 1, 1, true); g.setColor(Color.green); g.draw3DRect(dx + i,dy + 300-errG, 1, 1, true); g.setColor(Color.blue); g.draw3DRect(dx + i,dy + 300-errB, 1, 1, true); g2.setColor(Color.black); g2.draw3DRect(i,300-err, 1, 1, true); g2.setColor(Color.red); g2.draw3DRect(i,300-errR, 1, 1, true); g2.setColor(Color.green); g2.draw3DRect(i,300-errG, 1, 1, true); g2.setColor(Color.blue); g2.draw3DRect(i,300-errB, 1, 1, true); label7.setText(""+(int)(((double)(i*100))/255.0)+"%"); errRed = errGreen = errBlue = 0; } // set labels underneath the button label7.setText("100%"); label8.setText("min: " + min[1] + "(@" + min[0] + ")"); label9.setText("max: " + max[1] + "(@" + max[0] + ")"); } else { // we will be drawing a single image label7.setText(""); label8.setText(""); label9.setText(""); switch(index) { // find out which routine to call case 0: f_s(jSlider1.getValue(), jSlider2.getValue(), jSlider3.getValue()); break; case 1: dd = M1; // set matrix to use in calculation d_d(jSlider1.getValue(), jSlider2.getValue(), jSlider3.getValue()); break; case 2: dd = M2; d_d(jSlider1.getValue(), jSlider2.getValue(), jSlider3.getValue()); break; case 3: halftone(jSlider1.getValue(), jSlider2.getValue(), jSlider3.getValue()); break; default: } } repaint(); } public double f_s(int threshRed, int threshGreen, int threshBlue) { //calculate the floyd-steinberg halftoning // precalculated values for alpha and beta (from class notes) double alpha_err = 0.4375, beta = 0.1875, gamma = 0.3125, theta = 0.0625; double error; double cumulError = 0; pixels = new int[imw*imh]; // copy the image onto a new array si we can keep the old one's values // for calculating errors for (int i = 0; i < imw*imh; i++) pixels[i] = pix[i]; // now go through the pixelated image and perform floyd-steinberg's magic for(int row =0;row>24)&255, red = (pixel>>16)&255, green = (pixel>> 8)&255, blue = pixel&255; //the new, threshholded values // this is where halftoning is performed int nred = red>threshRed ? 255 : 0, ngreen = green>threshGreen ? 255 : 0, nblue = blue>threshBlue ? 255 : 0; // compute floyd-steinberg error values. double errR = (double)(red - nred); double errG = (double)(green - ngreen); double errB = (double)(blue - nblue); // compute cumulative errs for different colors errRed += Math.abs(errR); errGreen += Math.abs(errG); errBlue += Math.abs(errB); // compute global cumulative error cumulError += Math.abs(errR) + Math.abs(errG) + Math.abs(errB); // compuse pixel out of the new RGB values pixel = alpha==0?0:alpha<<24|nred<<16|ngreen<<8|nblue; pixels[row*imw+col] = pixel; // put back the pixel // we need to distribute the error to neighbours, but we // don't know whether we have any neighbours (what if we're // in the bottom right end of the image?) // see which ones we need to distribute to... if (col < imw - 1) { pixel = pixels[row*imw + col + 1]; //get pixel and calculate it with the error from current //pixel... this is the essence of the F-S algorithm red = ((pixel >> 16) & 255) + (int)(errR*alpha_err); green = ((pixel >> 8) & 255) + (int)(errG*alpha_err); blue = (pixel&255) + (int)(errB*alpha_err); alpha = (pixel>>24)&255; // check for overflowed pixels!!! red = red>255?255:red; red = red < 0?0:red; green = green>255?255:green; green = green < 0?0:green; blue = blue>255?255:blue; blue = blue < 0?0:blue; pixels[row*imw + col + 1] = alpha==0?0:alpha<<24|red<<16|green<<8|blue; } if(col > 0 && row < imh - 1) { pixel = pixels[(row+1)*imw + col - 1]; red = ((pixel >> 16) & 255) + (int)(errR*beta); green = ((pixel >> 8) & 255) + (int)(errG*beta); blue = (pixel&255) + (int)(errB*beta); alpha = (pixel>>24)&255; red = red>255?255:red; red = red < 0?0:red; green = green>255?255:green; green = green < 0?0:green; blue = blue>255?255:blue; blue = blue < 0?0:blue; pixels[(row+1)*imw + col - 1] = alpha==0?0:alpha<<24|red<<16|green<<8|blue; } if(row < imh - 1) { pixel = pixels[(row+1)*imw + col]; red = ((pixel >> 16) & 255) + (int)(errR*gamma); green = ((pixel >> 8) & 255) + (int)(errG*gamma); blue = (pixel&255) + (int)(errB*gamma); alpha = (pixel>>24)&255; red = red>255?255:red; red = red < 0?0:red; green = green>255?255:green; green = green < 0?0:green; blue = blue>255?255:blue; blue = blue < 0?0:blue; pixels[(row+1)*imw + col] = alpha==0?0:alpha<<24|red<<16|green<<8|blue; } if(col < imw - 1 && row < imh - 1) { pixel = pixels[(row+1)*imw + col + 1]; red = ((pixel >> 16) & 255) + (int)(errR*theta); green = ((pixel >> 8) & 255) + (int)(errG*theta); blue = (pixel&255) + (int)(errB*theta); alpha = (pixel>>24)&255; red = red>255?255:red; red = red < 0?0:red; green = green>255?255:green; green = green < 0?0:green; blue = blue>255?255:blue; blue = blue < 0?0:blue; pixels[(row+1)*imw + col + 1] = alpha==0?0:alpha<<24|red<<16|green<<8|blue; } } } if(!checkbox1.getState()) { // draw the image if we're not graphing MemoryImageSource mis = new MemoryImageSource(imw,imh,defaultRGB,pixels,0,imw); image_out = createImage(mis); // create an image } return(cumulError); } public double halftone(int threshRed, int threshGreen, int threshBlue) { //calculate basic (threshold) halftoning // not commented simply because it matches the original too closely //double errRed = 0, errGreen = 0, errBlue = 0; double cumulError = 0; pixels = new int[imw*imh]; for (int i = 0; i < imw*imh; i++) pixels[i] = pix[i]; for(int row =0;row> 16) & 255, green = (pixel >> 8) & 255, blue = pixel&255, alpha = (pixel>>24)&255; // extract colour info red = red>threshRed ? 255 : 0; green = green>threshGreen ? 255 : 0; blue = blue>threshBlue ? 255 : 0; int errpix = ((int[])pixels)[row*imw+col]; double errR = (double)(defaultRGB.getRed(errpix) - red), errG = (double)(defaultRGB.getGreen(errpix) - green), errB = (double)(defaultRGB.getBlue(errpix) - blue); cumulError += Math.abs(errR) + Math.abs(errG) + Math.abs(errB); pixel = alpha==0?0:alpha<<24|red<<16|green<<8|blue; pixels[row*imw+col] = pixel; // put back the pixel errRed += Math.abs(errR); errGreen += Math.abs(errG); errBlue += Math.abs(errB); } } if (!checkbox1.getState()) { MemoryImageSource mis = new MemoryImageSource(imw,imh,defaultRGB,pixels,0,imw); image_out = createImage(mis); // create an image } return cumulError; } public double d_d(int threshRed, int threshGreen, int threshBlue) { //Knuth's dot diffusion algorithm with small changes to accustom it //to this assignment. changes of course in the implementation, not //in the actual algorithm /************************ The arrays below are used to calculate the neighbours of the current pixel within the 64x64 grid -- if it's at the top left corner then it has only 3 neighbours to distribute error to, if it's in the middle, it may have up to 8!... The arrays have been alligned in such a way so that they look as if they are geometrically correspondent to the actual 3x3 arrangement the pixel would have. U, L, B, R mean 'Up', 'Left', 'Bottom', 'R' and correspond to the actual location of the pixel on the grid. for any particular A[i,j], we calculate the neighbours by: for(n = 0, n < array.length, n+=2) naighbour = A[i+n[0], j+n[1]; ************************************/ // an n by two array for our neighbours int neighbours[] = { -1, -1, -1, 0, -1,1, 0, -1, 0,1, 1, -1, 1, 0, 1,1, }; // neighbours at up left corner int neighboursUL[] = { 0, 1, 1, 0, 1, 1,}; // up right int neighboursUR[] = { 0, -1, 1, -1, 1, 0, }; // bottom right int neighboursBR[] = { -1, -1, -1, 0, 0, -1, }; // bottom left int neighboursBL[] = { -1, 0, -1, 1, 0, 1, }; // bottom int neighboursB[] = { -1, -1, -1, 0, -1, 1, 0, -1, 0, 1, }; // left int neighboursL[] = { -1, 0, -1, 1, 0, 1, 1, 0, 1, 1, }; // right int neighboursR[] = { -1, -1, -1, 0, 0, -1, 1, -1, 1, 0, }; // up int neighboursU[] = { 0, -1, 0, 1, 1, -1, 1, 0, 1, 1,}; double cumulError = 0; int nbrs[] = {}; // what neighbours do we have to calculate? pixels = new int[imw*imh]; //copy pixels for (int i = 0; i < imw*imh; i++) pixels[i] = pix[i]; // setup weighted error for this class and all the neighbours double W = 0; double Wij = 0; //note that the matrix used is of order N=8 // cycle through 1/8th of the array, essentially we jump in 8x8 = 64 // increments, leaving room for the matrices used. for(int row = 0; row < imw/8; row++) { for(int col = 0; col < imh/8; col++) { // now distribute the error within each subimage for(int k = 0; k < 64; k++) { for(int clx = 0; clx < 8; clx++) { for(int cly = 0; cly < 8; cly++) { int i = row*8+clx; int j = col*8+cly; // try to math a class with the current pixel if(dd[(clx*8) + cly] == k) { // matched class pixel = ((int[])pixels)[i*imw+j]; int errpix = ((int[])pixels)[i*imw+j]; int red = (pixel >> 16) & 255, green = (pixel >> 8) & 255, blue = pixel&255, alpha = (pixel>>24)&255; // extract colour info int nred = red>threshRed ? 255 : 0, ngreen = green>threshGreen ? 255 : 0, nblue = blue>threshBlue ? 255 : 0; // calc errors and put back the pixel double errR = (double)(red - nred), errG = (double)(green - ngreen), errB = (double)(blue - nblue); cumulError += Math.abs(errR) + Math.abs(errG) + Math.abs(errB); pixel = alpha==0?0:alpha<<24|nred<<16|ngreen<<8|nblue; pixels[i*imw+j] = pixel; // put back the pixel errRed += Math.abs(errR); errGreen += Math.abs(errG); errBlue += Math.abs(errB); // see which neighbours we should visit, use // the matrices defined at the beginning of // the routine if(clx != 0 && cly != 0 && clx != 7 && cly != 7) nbrs = neighbours; else if(clx == 0 && cly == 0) nbrs = neighboursUL; else if(clx == 0 && cly == 7) nbrs = neighboursUR; else if(clx == 0) nbrs = neighboursU; else if(clx == 7 && cly == 0) nbrs = neighboursBL; else if(clx == 7 && cly == 7) nbrs = neighboursBR; else if(clx == 7) nbrs = neighboursB; else if(cly == 0) nbrs = neighboursL; else if(cly == 7) nbrs = neighboursR; W = 0; // calculate the additive weight for (int neigh = 0; neigh < nbrs.length; neigh+=2) { if(dd[(clx+nbrs[neigh])*8 + (cly+nbrs[neigh+1])] > k) W += weight(nbrs[neigh],nbrs[neigh+1]); } /* no difference from class notes */ if(W > 0) { if(clx != 0 && cly != 0 && clx != 7 && cly != 7) nbrs = neighbours; else if(clx == 0 && cly == 0) nbrs = neighboursUL; else if(clx == 0 && cly == 7) nbrs = neighboursUR; else if(clx == 0) nbrs = neighboursU; else if(clx == 7 && cly == 0) nbrs = neighboursBL; else if(clx == 7 && cly == 7) nbrs = neighboursBR; else if(clx == 7) nbrs = neighboursB; else if(cly == 0) nbrs = neighboursL; else if(cly == 7) nbrs = neighboursR; else nbrs = null; for ( int neigh = 0;nbrs!= null && neigh < nbrs.length; neigh+=2) { // this statement was taken from // knuth's implementation of the // algorithm but did not appear in // class notes!!!!!!!!!! // it works damn well with the // statement though if(dd[(clx+nbrs[neigh])*8 + (cly+nbrs[neigh+1])] > k) { pixel = pixels[(i+nbrs[neigh])*imw + (j+nbrs[neigh+1])]; // distribute error Wij = weight(nbrs[neigh],nbrs[neigh+1])/W; red = ((pixel >> 16) & 255) + (int)Math.round(errR*Wij); green = ((pixel >> 8) & 255) + (int)Math.round(errG*Wij); blue = (pixel&255) + (int)Math.round(errB*Wij); alpha = (pixel>>24)&255; // check for overflowed pixels!!! red = red>255?255:red; red = red < 0?0:red; green = green>255?255:green; green = green < 0?0:green; blue = blue>255?255:blue; blue = blue < 0?0:blue; //put back pixel pixel = alpha==0?0:alpha<<24|red<<16|green<<8|blue; pixels[(i+nbrs[neigh])*imw + (j+nbrs[neigh+1])] = pixel; } } } } } } } } } if (!checkbox1.getState()) { MemoryImageSource mis = new MemoryImageSource(imw,imh,defaultRGB,pixels,0,imw); image_out = createImage(mis); // create an image } return cumulError; } private double weight(int x, int y) { //calculate the weight for dot-diffusion return 3 - (x*x + y*y); } }