/* * Copyright 2017 Laszlo Balazs-Csiki * * This file is part of Pixelitor. Pixelitor is free software: you * can redistribute it and/or modify it under the terms of the GNU * General Public License, version 3 as published by the Free * Software Foundation. * * Pixelitor is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Pixelitor. If not, see <http://www.gnu.org/licenses/>. */ package pixelitor.filters; import com.jhlabs.image.BoxBlurFilter; import pixelitor.filters.gui.AngleParam; import pixelitor.filters.gui.BooleanParam; import pixelitor.filters.gui.ColorParam; import pixelitor.filters.gui.GroupedRangeParam; import pixelitor.filters.gui.ParamSet; import pixelitor.filters.gui.RangeParam; import pixelitor.filters.gui.ShowOriginal; import pixelitor.utils.BasicProgressTracker; import pixelitor.utils.ImageUtils; import pixelitor.utils.ProgressTracker; import pixelitor.utils.ReseedSupport; import pixelitor.utils.Utils; import java.awt.AlphaComposite; import java.awt.Composite; import java.awt.Graphics2D; import java.awt.Paint; import java.awt.Rectangle; import java.awt.Shape; import java.awt.TexturePaint; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.util.Random; import static java.awt.AlphaComposite.SRC_OVER; import static java.awt.Color.BLACK; import static java.awt.Color.WHITE; import static java.awt.RenderingHints.KEY_ANTIALIASING; import static java.awt.RenderingHints.KEY_INTERPOLATION; import static java.awt.RenderingHints.VALUE_ANTIALIAS_ON; import static java.awt.RenderingHints.VALUE_INTERPOLATION_BILINEAR; import static pixelitor.filters.gui.ColorParam.OpacitySetting.USER_ONLY_OPACITY; /** * Photo Collage */ public class PhotoCollage extends FilterWithParametrizedGUI { public static final String NAME = "Photo Collage"; private final GroupedRangeParam size = new GroupedRangeParam("Photo Size", 40, 200, 999); private final RangeParam marginSize = new RangeParam("Margin", 0, 5, 20); private final RangeParam imageNumber = new RangeParam("Number of Images", 1, 10, 100); private final RangeParam randomRotation = new RangeParam("Random Rotation Amount (%)", 0, 100, 100); private final BooleanParam allowOutside = new BooleanParam("Allow Outside", true); private final ColorParam bgColor = new ColorParam("Background Color", BLACK, USER_ONLY_OPACITY); private final RangeParam shadowOpacityParam = new RangeParam("Shadow Opacity (%)", 0, 80, 100); private final AngleParam shadowAngleParam = new AngleParam("Shadow Angle", 0.7); private final RangeParam shadowDistance = new RangeParam("Shadow Distance", 0, 5, 20); private final RangeParam shadowSoftnessParam = new RangeParam("Shadow Softness", 0, 3, 10); public PhotoCollage() { super(ShowOriginal.YES); setParamSet(new ParamSet( imageNumber, size.withAdjustedRange(1.0), randomRotation, allowOutside, marginSize.withAdjustedRange(0.02), bgColor, shadowOpacityParam, shadowAngleParam, shadowDistance.withAdjustedRange(0.02), shadowSoftnessParam.withAdjustedRange(0.01) ).withAction(ReseedSupport.createAction())); } @Override public BufferedImage doTransform(BufferedImage src, BufferedImage dest) { int numImages = imageNumber.getValue(); ProgressTracker pt = new BasicProgressTracker(NAME, numImages); ReseedSupport.reInitialize(); Random rand = ReseedSupport.getRand(); int xSize = size.getValue(0); int ySize = size.getValue(1); // fill with the background color Graphics2D g = dest.createGraphics(); g.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON); g.setRenderingHint(KEY_INTERPOLATION, VALUE_INTERPOLATION_BILINEAR); g.setColor(bgColor.getColor()); g.fillRect(0, 0, dest.getWidth(), dest.getHeight()); Rectangle fullImageRect = new Rectangle(0, 0, xSize, ySize); int margin = marginSize.getValue(); Rectangle imageRect = new Rectangle(fullImageRect); imageRect.grow(-margin, -margin); Paint imagePaint = new TexturePaint(src, new Rectangle2D.Float(0, 0, src.getWidth(), src.getHeight())); // the shadow image must be larger than the image size so that there is room for soft shadows int shadowSoftness = shadowSoftnessParam.getValue(); int softShadowRoom = 1 + (int) (2.3 * shadowSoftness); int shadowImgWidth = xSize + 2 * softShadowRoom; int shadowImgHeight = ySize + 2 * softShadowRoom; BufferedImage shadowImage = ImageUtils.createSysCompatibleImage(shadowImgWidth, shadowImgHeight); Graphics2D gShadow = shadowImage.createGraphics(); gShadow.setColor(BLACK); gShadow.fillRect(softShadowRoom, softShadowRoom, xSize, ySize); gShadow.dispose(); if (shadowSoftness > 0) { shadowImage = new BoxBlurFilter(shadowSoftness, shadowSoftness, 1, NAME) .filter(shadowImage, shadowImage); } Point2D offset = Utils.calculateOffset(shadowDistance.getValue(), shadowAngleParam.getValueInRadians()); int shadowOffsetX = (int) offset.getX(); int shadowOffsetY = (int) offset.getY(); // multiply makes sense only if the shadow color is not black Composite shadowComposite = AlphaComposite.getInstance(SRC_OVER, shadowOpacityParam.getValueAsPercentage()); for (int i = 0; i < numImages; i++) { // Calculate the transform of the image // step 2: translate int tx; int ty; if (allowOutside.isChecked()) { tx = rand.nextInt(dest.getWidth() + xSize) - xSize; ty = rand.nextInt(dest.getHeight() + ySize) - ySize; } else { int maxTranslateX = dest.getWidth() - xSize; int maxTranslateY = dest.getHeight() - ySize; if (maxTranslateX <= 0) { maxTranslateX = 1; } if (maxTranslateY <= 0) { maxTranslateY = 1; } tx = rand.nextInt(maxTranslateX); ty = rand.nextInt(maxTranslateY); } AffineTransform randomTransform = AffineTransform.getTranslateInstance(tx, ty); // step 1: rotate // the rotation amount is a number between -PI and PI if maxRandomRot is 1.0; double theta = Math.PI * 2 * rand.nextFloat() - Math.PI; theta *= randomRotation.getValueAsPercentage(); randomTransform.rotate(theta, xSize / 2.0, ySize / 2.0); // Calculate the transform of the shadow // step 3: final shadow offset AffineTransform shadowTransform = AffineTransform.getTranslateInstance(shadowOffsetX, shadowOffsetY); // step 2: rotate and random translate shadowTransform.concatenate(randomTransform); // step 1: take shadowBorder into account shadowTransform.translate(-softShadowRoom, -softShadowRoom); // Draw the shadow g.setComposite(shadowComposite); g.drawImage(shadowImage, shadowTransform, null); // Draw the margin and the image g.setComposite(AlphaComposite.getInstance(SRC_OVER)); Shape transformedRect = randomTransform.createTransformedShape(fullImageRect); Shape transformedImageRect; if (margin > 0) { transformedImageRect = randomTransform.createTransformedShape(imageRect); g.setColor(WHITE); g.fill(transformedRect); } else { transformedImageRect = transformedRect; } g.setPaint(imagePaint); g.fill(transformedImageRect); pt.unitDone(); } pt.finish(); g.dispose(); return dest; } }