// Sketch CSC480TrimTransparent17Apr2020 // Sketch: CSC480TrimTransparent, D. Parson, 4/15/2020 PImage ORIGINALIMAGE, FITTEDIMAGE ; TrimmedImageWithExtents TRIMMEDIMAGEWITHEXTENTS ; void setup() { //size(1175,433,P2D); // exact match to the test image //size(900, 1000, P2D); size(1000, 800, P2D); //fullScreen(); ORIGINALIMAGE = loadImage("BoundaryTransparent.png"); FITTEDIMAGE = fitImageIntoSize(ORIGINALIMAGE, width, height); TRIMMEDIMAGEWITHEXTENTS = trimImage(FITTEDIMAGE) ; } void draw() { background(0); imageMode(CENTER); rectMode(CENTER); stroke(255); strokeWeight(2); noFill(); PImage timg = TRIMMEDIMAGEWITHEXTENTS.TrimmedImage ; image(timg, width/2, height/2); rect(width/2, height/2, timg.width, timg.height); //image(FITTEDIMAGE, width/2, height/2); rect(width/2, height/2, FITTEDIMAGE.width, FITTEDIMAGE.height); // Plot the extents as white text. This is for illustration/debugging only. push(); fill(255); textSize(28); // Find left,top location of the original image: int leftOriginal = (width - FITTEDIMAGE.width) / 2 ; int topOriginal = (height - FITTEDIMAGE.height) / 2 ; translate(leftOriginal, topOriginal); // plot debugging text relative to centered image textAlign(LEFT, TOP); // make it fit inside the trimmed image text("" + TRIMMEDIMAGEWITHEXTENTS.leftx + "," + TRIMMEDIMAGEWITHEXTENTS.topy, TRIMMEDIMAGEWITHEXTENTS.leftx, TRIMMEDIMAGEWITHEXTENTS.topy); textAlign(RIGHT, BOTTOM); text("" + TRIMMEDIMAGEWITHEXTENTS.rightx + "," + TRIMMEDIMAGEWITHEXTENTS.bottomy, TRIMMEDIMAGEWITHEXTENTS.rightx, TRIMMEDIMAGEWITHEXTENTS.bottomy); pop(); textAlign(CENTER, BOTTOM); text("Original is " + ORIGINALIMAGE.width + " X " + ORIGINALIMAGE.height + ", FITTED IS " + FITTEDIMAGE.width + " X " + FITTEDIMAGE.height + ", Trimmed copy is " + timg.width + " X " + timg.height, width/2, height - 1); } /** * Helper class TrimmedImageWithExtents is returned by function * trimImage() to return both a PImage with border transparent pixels * trimmed away from it similar to the Photoshop Image->Trim command for * transparent boundary pixels, and it returns the leftx, topy TO * rightx,bottomy extents of the returned trimmed image within the original * image. See trimImage(). **/ class TrimmedImageWithExtents { final PImage TrimmedImage ; final int leftx, topy, rightx, bottomy ; TrimmedImageWithExtents(PImage TrimmedImage, int leftx, int topy, int rightx, int bottomy) { this.TrimmedImage = TrimmedImage ; this.leftx = leftx ; this.topy = topy ; this.rightx = rightx ; this.bottomy = bottomy ; } } /** * trimImage() trims away outer transparent pixels in a copy of the incoming * inImage, returning both the trimmed copy PImage and its location in the * inImage within a TrimmedImageWithExtents objects. When inImage is completely * transparent, trimImage() returns a null pointer. If there is no transparent * boundary area, trimImage() just returns a copy of inImage. **/ TrimmedImageWithExtents trimImage(PImage inImage) { PImage outImage = null ; // 1. How big is outImage and what are its extents? int xleft = -1, xright = -1, ytop = -1, ybottom = -1 ; inImage.loadPixels(); // Search rows from top to bottom until finding a non-transparent pixel. for (int row = 0; row < inImage.height && ytop < 0; row++) { for (int col = 0; col < inImage.width && ytop < 0; col++) { // Two-hex-digit byes if ((inImage.pixels[row * inImage.width + col] & 0xff000000) != 0) { ytop = row ; } } } // Search rows from bottom to top until finding a non-transparent pixel. for (int row = inImage.height-1; row >= 0 && ybottom < 0; row--) { for (int col = 0; col < inImage.width && ybottom < 0; col++) { if ((inImage.pixels[row * inImage.width + col] & 0xff000000) != 0) { ybottom = row ; } } } // Search columns from left to right until finding a non-transparent pixel. for (int col = 0; col < inImage.width && xleft < 0; col++) { for (int row = ytop; row <= ybottom && xleft < 0; row++) { if ((inImage.pixels[row * inImage.width + col] & 0xff000000) != 0) { xleft = col ; } } } // Search columns from right to left until finding a non-transparent pixel. for (int col = inImage.width-1; col >= 0 && xright < 0; col--) { for (int row = ytop; row <= ybottom && xright < 0; row++) { if ((inImage.pixels[row * inImage.width + col] & 0xff000000) != 0) { xright = col ; } } } if (ytop < 0 || ybottom < 0 || xleft < 0 || xright < 0) { inImage.updatePixels(); return null ; // null means all pixels are transparent } outImage = createImage(xright-xleft+1, ybottom-ytop+1, ARGB); outImage.loadPixels(); for (int row = ytop, outrow = 0; row <= ybottom; row++, outrow++) { for (int col = xleft, outcol = 0; col <= xright; col++, outcol++) { outImage.pixels[outrow * outImage.width + outcol] = inImage.pixels[row * inImage.width + col]; } } inImage.updatePixels(); outImage.updatePixels(); return new TrimmedImageWithExtents(outImage, xleft, ytop, xright, ybottom) ; } /** * Fit a copy of the original PImage into the size specified by fitwidth X fitheight. * Return this copy. **/ PImage fitImageIntoSize(PImage original, float fitwidth, float fitheight) { PImage result = original.copy(); float fitaspect = fitwidth / fitheight ; float imgaspect = float(original.width) / float(original.height); //println("DEBUG fit dimensions = " + fitwidth + " X " + fitheight); //println("DEBUG original dimensions = " + original.width + " X " + original.height); //println("DEBUG fitaspect = " + fitaspect + ", imgaspect = " + imgaspect); if (fitaspect == imgaspect) { if (original.width != fitwidth) { result.resize(round(fitwidth), round(fitheight)); } } else if (fitaspect > imgaspect) { // fit frame has plenty of width, height is the limiting factor float scaler = fitheight / original.height ; result.resize(round(original.width*scaler), round(original.height*scaler)); } else { // fit frame has plenty of height, width is the limiting factor float scaler = fitwidth / original.width ; result.resize(round(original.width*scaler), round(original.height*scaler)); } return result ; }