| 1 | To implement a plugin for JPsychoMorph you need to create a Java class that overrides the Facemorph.Batchable interface: |
| 2 | |
| 3 | {{{ |
| 4 | public interface Batchable { |
| 5 | public boolean process(ImageZoomPanel izp, boolean single); |
| 6 | public boolean initialise(PsychoMorphForm psychomorph); |
| 7 | public void finish(); |
| 8 | public String getName(); |
| 9 | } |
| 10 | }}} |
| 11 | |
| 12 | The main method is ''process'' which will be called for each image in the batch. The ''single'' parameter is true if the processing is '''not''' operating in batch mode, but on a single loaded image. The ImageZoomPanel allows access to the currently loaded Image and Template. |
| 13 | |
| 14 | The ''initialise'' method is called once before a batch of images, or before a single image is processed. It allows access to the rest of the system state via the PsychoMorphForm parameter. The ''finish'' method is called after all the images in the list have been processed, it is not called when operating on a single image. |
| 15 | |
| 16 | The ''getName'' method should return the name of the Batchable for inclusion in the ''Plugins'' menu on the psychomorph Transform window. Both Batch and single versions of the plugin will be added to the menu. |
| 17 | |
| 18 | The class must also have a no parameter (default) constructor for initialising. This is called both when the plugin is first loaded into the system and every time the (single or batch) menu item is clicked. |
| 19 | |
| 20 | == Examples == |
| 21 | |
| 22 | === Simple example === |
| 23 | |
| 24 | An example of a simple Batchable is given below. This Batchable checks that the inner lip points are correctly positioned vertically, and swaps corresponding points if needed. |
| 25 | |
| 26 | {{{ |
| 27 | import Facemorph.Template; |
| 28 | import Facemorph.psychomorph.Batchable; |
| 29 | import Facemorph.psychomorph.ImageZoomPanel; |
| 30 | import Facemorph.psychomorph.PsychoMorphForm; |
| 31 | import java.awt.geom.Point2D; |
| 32 | |
| 33 | /** |
| 34 | * Batch corrects the mouth in the "standard" template, by checking if the top -lip is below the bottom lip points and swapping if needed |
| 35 | */ |
| 36 | public class BatchCorrectMouth implements Batchable { |
| 37 | |
| 38 | public boolean process(ImageZoomPanel izp, boolean single) { |
| 39 | Template tem = izp.getTemplate(); |
| 40 | int[] topMouth = {94, 95, 96, 97, 98}, bottomMouth = {99, 100, 101, 102, 103}; |
| 41 | |
| 42 | for (int i=0; i<topMouth.length; i++) { |
| 43 | Point2D.Float p = tem.getPoint(topMouth[i]); |
| 44 | Point2D.Float q = tem.getPoint(bottomMouth[i]); |
| 45 | if (q.y<p.y) { |
| 46 | float x = p.x, y=p.y; |
| 47 | p.x=q.x; p.y=q.y; |
| 48 | q.x=x; q.y=y; |
| 49 | } |
| 50 | } |
| 51 | tem.recalculateContours(); |
| 52 | izp.setTemplate(tem); |
| 53 | return true; |
| 54 | } |
| 55 | |
| 56 | public void finish() { } |
| 57 | |
| 58 | public String getName() { |
| 59 | return "Correct Mouth"; |
| 60 | } |
| 61 | |
| 62 | public boolean initialise(PsychoMorphForm psychomorph) { |
| 63 | return true; |
| 64 | } |
| 65 | } |
| 66 | }}} |
| 67 | |
| 68 | === Chimeric transforms === |
| 69 | |
| 70 | Here is the chimeric transform plugin source code, it uses some built in functionality and gets input from the user: |
| 71 | |
| 72 | {{{ |
| 73 | |
| 74 | import Facemorph.Template; |
| 75 | import Facemorph.Transformer; |
| 76 | import Facemorph.psychomorph.Batchable; |
| 77 | import Facemorph.psychomorph.DelineatorForm; |
| 78 | import Facemorph.psychomorph.ImageZoomPanel; |
| 79 | import Facemorph.psychomorph.PsychoMorphForm; |
| 80 | import java.awt.Image; |
| 81 | import java.awt.image.BufferedImage; |
| 82 | import javax.swing.JOptionPane; |
| 83 | |
| 84 | /** |
| 85 | * Chimeric transform implementation |
| 86 | */ |
| 87 | public class BatchChimericTransform implements Batchable { |
| 88 | PsychoMorphForm morphApp; |
| 89 | float l1, l2, wid; |
| 90 | |
| 91 | public boolean process(ImageZoomPanel izp, boolean single) { |
| 92 | BufferedImage leftImage = DelineatorForm.checkBufferedImage(morphApp.getLeftImage()); |
| 93 | Template leftTemplate = morphApp.getLeftTemplate(); |
| 94 | BufferedImage rightImage = DelineatorForm.checkBufferedImage(morphApp.getRightImage()); |
| 95 | Template rightTemplate = morphApp.getRightTemplate(); |
| 96 | BufferedImage img = DelineatorForm.checkBufferedImage(izp.getImage()); |
| 97 | |
| 98 | Template tem = izp.getTemplate(); |
| 99 | Template outTem = new Template(); |
| 100 | |
| 101 | if (single) { |
| 102 | morphApp.getDelineator().getImageUndoStack().push(img); |
| 103 | morphApp.getDelineator().getTemplateUndoStack().push(tem); |
| 104 | morphApp.getDelineator().getTransformUndoMenuItem().setEnabled(true); |
| 105 | } |
| 106 | |
| 107 | Image outImg = Transformer.transformChimeric(morphApp.getWarpType(), tem, leftTemplate, rightTemplate, outTem, |
| 108 | img, leftImage, rightImage, l1, l2, wid, true, false); |
| 109 | izp.setImage(outImg); |
| 110 | izp.setTemplate(outTem); |
| 111 | return true; |
| 112 | } |
| 113 | |
| 114 | public void finish() { } |
| 115 | |
| 116 | public String getName() { |
| 117 | return "Transform Chimeric"; |
| 118 | } |
| 119 | |
| 120 | public boolean initialise(PsychoMorphForm psychomorph) { |
| 121 | morphApp = psychomorph; |
| 122 | |
| 123 | String leftStr = JOptionPane.showInputDialog(morphApp.getDelineator(), "Transform level for left side", "1"); |
| 124 | String rightStr = JOptionPane.showInputDialog(morphApp.getDelineator(), "Transform level for right side", "0"); |
| 125 | int w = 50; |
| 126 | String widthStr = JOptionPane.showInputDialog(morphApp.getDelineator(), "Width of smoothing band", "" + w); |
| 127 | l1 = Float.parseFloat(leftStr); |
| 128 | l2 = Float.parseFloat(rightStr); |
| 129 | wid = Float.parseFloat(widthStr); |
| 130 | return true; |
| 131 | } |
| 132 | |
| 133 | } |
| 134 | |
| 135 | |
| 136 | }}} |
| 137 | |
| 138 | |
| 139 | === Advanced example === |
| 140 | |
| 141 | In this example we actually mess about with warping images and modifying the colours, in order to symmetrise and image in shape and / or colour. |
| 142 | |
| 143 | {{{ |
| 144 | |
| 145 | import Facemorph.Template; |
| 146 | import Facemorph.Warp; |
| 147 | import Facemorph.psychomorph.Batchable; |
| 148 | import Facemorph.psychomorph.DelineatorForm; |
| 149 | import Facemorph.psychomorph.ImageZoomPanel; |
| 150 | import Facemorph.psychomorph.PsychoMorphForm; |
| 151 | import java.awt.Color; |
| 152 | import java.awt.image.BufferedImage; |
| 153 | import java.io.File; |
| 154 | import java.io.FileNotFoundException; |
| 155 | import java.io.IOException; |
| 156 | import javax.swing.JFileChooser; |
| 157 | import javax.swing.JOptionPane; |
| 158 | |
| 159 | /** |
| 160 | * Batch symmetriser |
| 161 | */ |
| 162 | public class BatchSymmetrise implements Batchable { |
| 163 | int[] plist; |
| 164 | PsychoMorphForm psychomorph; |
| 165 | |
| 166 | public boolean process(ImageZoomPanel izp, boolean single) { |
| 167 | Template symtemp = new Template(); |
| 168 | Template template = izp.getTemplate(); |
| 169 | BufferedImage img = DelineatorForm.checkBufferedImage(izp.getImage()); |
| 170 | |
| 171 | if (single) { |
| 172 | psychomorph.getDelineator().getImageUndoStack().push(img); |
| 173 | psychomorph.getDelineator().getTemplateUndoStack().push(template); |
| 174 | psychomorph.getDelineator().getTransformUndoMenuItem().setEnabled(true); |
| 175 | } |
| 176 | symtemp.symmetrise(template, plist, img.getWidth()); |
| 177 | symtemp.copySamples(template); |
| 178 | Warp warp = Warp.createWarp(psychomorph.getWarpType(), img.getWidth(), img.getHeight(), img.getWidth(), img.getHeight(), false); |
| 179 | warp.interpolate(template, symtemp, true, true, psychomorph.getOverlap()); |
| 180 | img = warp.warpImage(img); |
| 181 | if (psychomorph.getDelineator().getColourCheckBoxMenuItem().getState()) { |
| 182 | symmetrise(img); |
| 183 | } |
| 184 | if (psychomorph.getDelineator().getShapeCheckBoxMenuItem().getState()) { |
| 185 | izp.setTemplate(symtemp); |
| 186 | } else { |
| 187 | //warp = new MultiscaleWarp(img.getWidth(), img.getHeight()); |
| 188 | warp = Warp.createWarp(psychomorph.getWarpType(), img.getWidth(), img.getHeight(), img.getWidth(), img.getHeight(), false); |
| 189 | warp.interpolate(symtemp, template, true, true, psychomorph.getOverlap()); |
| 190 | img = warp.warpImage(img); |
| 191 | |
| 192 | } |
| 193 | izp.setImage(img); |
| 194 | return true; |
| 195 | } |
| 196 | |
| 197 | public boolean initialise(PsychoMorphForm psychomorph) { |
| 198 | this.psychomorph = psychomorph; |
| 199 | JFileChooser chooser = PsychoMorphForm.setUpFileDialog(psychomorph.getFileChooser(), "Symmetry File", "sym"); |
| 200 | psychomorph.setFileChooser(chooser); |
| 201 | |
| 202 | int ok = psychomorph.getFileChooser().showOpenDialog(psychomorph.getDelineator()); |
| 203 | File f2 = psychomorph.getFileChooser().getSelectedFile(); |
| 204 | plist = null; |
| 205 | if (f2 == null || ok != JFileChooser.APPROVE_OPTION) return false; |
| 206 | |
| 207 | try { |
| 208 | plist = Template.readSymFile(f2.getPath()); |
| 209 | } catch (FileNotFoundException ex) { |
| 210 | JOptionPane.showMessageDialog(psychomorph.getDelineator(), "Error reading sym file " + f2 + ", error " + ex, "Symmetry file read error", JOptionPane.ERROR_MESSAGE); |
| 211 | ex.printStackTrace(); |
| 212 | return false; |
| 213 | } catch (IOException ex) { |
| 214 | JOptionPane.showMessageDialog(psychomorph.getDelineator(), "Error reading sym file " + f2 + ", error " + ex, "Symmetry file read error", JOptionPane.ERROR_MESSAGE); |
| 215 | ex.printStackTrace(); |
| 216 | return false; |
| 217 | } |
| 218 | return true; |
| 219 | } |
| 220 | |
| 221 | public void finish() { } |
| 222 | |
| 223 | public String getName() { |
| 224 | return "Symmetrise"; |
| 225 | } |
| 226 | |
| 227 | void symmetrise(BufferedImage bimg) { |
| 228 | int x, y, r, g, b; |
| 229 | int r1, g1, b1, r2, g2, b2; |
| 230 | |
| 231 | for (x=0; x<bimg.getWidth()/2; x++) |
| 232 | for (y=0; y<bimg.getHeight(); y++) |
| 233 | { |
| 234 | int rgb1 = bimg.getRGB(x, y); |
| 235 | Color c1 = new Color(rgb1); |
| 236 | r1 = c1.getRed(); |
| 237 | g1 = c1.getGreen(); |
| 238 | b1 = c1.getBlue(); |
| 239 | int rgb2 = bimg.getRGB(bimg.getWidth()-x-1, y); |
| 240 | Color c2 = new Color(rgb2); |
| 241 | r2 = c2.getRed(); |
| 242 | g2 = c2.getGreen(); |
| 243 | b2 = c2.getBlue(); |
| 244 | r = (r1+r2)/2; |
| 245 | g = (g1+g2)/2; |
| 246 | b = (b1+b2)/2; |
| 247 | Color c = new Color(r, g, b); |
| 248 | bimg.setRGB(x, y, c.getRGB()); |
| 249 | bimg.setRGB(bimg.getWidth()-x-1, y, c.getRGB()); |
| 250 | } |
| 251 | |
| 252 | } |
| 253 | } |
| 254 | |
| 255 | }}} |