/* * 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.history; import pixelitor.Composition; import pixelitor.layers.Drawable; import pixelitor.selection.Selection; import pixelitor.utils.ImageUtils; import pixelitor.utils.debug.DataBufferNode; import pixelitor.utils.debug.DebugNode; import javax.swing.undo.CannotRedoException; import javax.swing.undo.CannotUndoException; import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.awt.image.DataBuffer; import java.awt.image.Raster; import static pixelitor.Composition.ImageChangeActions.FULL; /** * Represents the changes made to a part of an image (for example brush strokes). * Only the affected pixels are saved in order to reduce overall memory usage */ public class PartialImageEdit extends FadeableEdit { private final Rectangle saveRect; private final boolean canRepeat; private Raster backupRaster; private final Drawable dr; public PartialImageEdit(String name, Composition comp, Drawable dr, BufferedImage image, Rectangle saveRect, boolean canRepeat) { super(comp, dr, name); this.canRepeat = canRepeat; this.dr = dr; this.saveRect = saveRect; backupRaster = image.getData(this.saveRect); } @Override public void undo() throws CannotUndoException { super.undo(); swapRasters(); } @Override public void redo() throws CannotRedoException { super.redo(); swapRasters(); } private void swapRasters() { BufferedImage image = dr.getImage(); Raster tmpRaster = null; try { tmpRaster = image.getData(saveRect); image.setData(backupRaster); } catch (ArrayIndexOutOfBoundsException e) { System.out.println("PartialImageEdit.swapRasters saveRect = " + saveRect); int width = image.getWidth(); int height = image.getHeight(); System.out.println("PartialImageEdit.swapRasters width = " + width + ", height = " + height); debugRaster("tmpRaster", tmpRaster); debugRaster("backupRaster", backupRaster); throw e; } backupRaster = tmpRaster; comp.imageChanged(FULL); dr.updateIconImage(); History.notifyMenus(this); } private static void debugRaster(String name, Raster raster) { if (raster == null) { System.err.printf("PartialImageEdit::debugRaster: NULL RASTER, name = '%s'%n", name); return; } Rectangle rasterBounds = raster.getBounds(); String className = raster.getClass().getSimpleName(); DataBuffer dataBuffer = raster.getDataBuffer(); int dataType = dataBuffer.getDataType(); String typeAsString = DataBufferNode.getDataBufferTypeDescription(dataType); int numBanks = dataBuffer.getNumBanks(); int numBands = raster.getNumBands(); int numDataElements = raster.getNumDataElements(); String msg = String.format("className = %s, rasterBounds = %s, dataType = %d, typeAsString=%s, numBanks = %d, numBands = %d, numDataElements = %d", className, rasterBounds, dataType, typeAsString, numBanks, numBands, numDataElements); System.out.println("PartialImageEdit::debugRaster debugging raster: " + name + ": " + msg); } @Override public void die() { super.die(); backupRaster = null; } @Override public boolean canRepeat() { return canRepeat; } @Override public BufferedImage getBackupImage() { // recreate the full image as if it was backed up entirely // because Fade expects to fade images of equal size // TODO this is not the optimal solution - Fade should fade only the changed area BufferedImage fullImage = dr.getImage(); BufferedImage previousImage = ImageUtils.copyImage(fullImage); previousImage.setData(backupRaster); Selection selection = dr.getComp().getSelection(); if (selection != null) { // backupRaster is relative to the full image, but we need to return a selection-sized image // TODO this is another ugly hack previousImage = dr.getSelectionSizedPartFrom(previousImage, selection, true); } return previousImage; } @Override public DebugNode getDebugNode() { DebugNode node = super.getDebugNode(); node.addIntChild("Backup Image Width", backupRaster.getWidth()); node.addIntChild("Backup Image Height", backupRaster.getHeight()); return node; } }