/* * 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.layers; import pixelitor.Composition; import pixelitor.history.History; import pixelitor.history.LinkLayerMaskEdit; import pixelitor.tools.Tools; import pixelitor.utils.ImageUtils; import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Composite; import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.IndexColorModel; import java.awt.image.WritableRaster; import static java.awt.AlphaComposite.DstIn; /** * A layer mask. */ public class LayerMask extends ImageLayer { private static final long serialVersionUID = 1L; private transient BufferedImage transparencyImage; public static final ColorModel TRANSPARENCY_COLOR_MODEL; public static final ColorModel RUBYLITH_COLOR_MODEL; private boolean linked = true; // whether it moves together with its parent layer static { byte[] lookup = new byte[256]; for (int i = 0; i < 256; i++) { lookup[i] = (byte) i; } TRANSPARENCY_COLOR_MODEL = new IndexColorModel(8, 256, lookup, // red lookup, // green lookup, // blue lookup); // alpha byte[] invertedLookup = new byte[256]; byte[] allZeroLookup = new byte[256]; for (int i = 0; i < 256; i++) { invertedLookup[i] = (byte) (255 - i); } RUBYLITH_COLOR_MODEL = new IndexColorModel(8, 256, invertedLookup, // red allZeroLookup, // green allZeroLookup, // blue invertedLookup); // alpha } public static final Composite RUBYLITH_COMPOSITE = AlphaComposite.SrcOver.derive(0.5f); public LayerMask(Composition comp, BufferedImage bwImage, Layer layer, boolean inheritTranslation) { super(comp, bwImage, layer.getName() + " MASK", layer); if (inheritTranslation && layer instanceof ContentLayer) { ContentLayer contentLayer = (ContentLayer) layer; translationX = contentLayer.getTX(); translationY = contentLayer.getTY(); } assert bwImage.getType() == BufferedImage.TYPE_BYTE_GRAY; } public void applyToImage(BufferedImage in) { Graphics2D g = in.createGraphics(); g.setComposite(DstIn); g.drawImage(getTransparencyImage(), 0, 0, null); g.dispose(); } public void updateFromBWImage() { assert image.getType() == BufferedImage.TYPE_BYTE_GRAY; assert image.getColorModel() != TRANSPARENCY_COLOR_MODEL; // The transparency image shares the raster data with the BW image, // but interprets the bytes differently. // Therefore this method needs to be called only when // the visible image reference changes. WritableRaster raster = getVisibleImage().getRaster(); this.transparencyImage = new BufferedImage(TRANSPARENCY_COLOR_MODEL, raster, false, null); } public void paintAsRubylith(Graphics2D g) { Composite oldComposite = g.getComposite(); WritableRaster raster = getVisibleImage().getRaster(); BufferedImage rubylithImage = new BufferedImage(RUBYLITH_COLOR_MODEL, raster, false, null); g.setComposite(RUBYLITH_COMPOSITE); g.drawImage(rubylithImage, 0, 0, null); g.setComposite(oldComposite); } @Override protected BufferedImage createEmptyImageForLayer(int width, int height) { // BufferedImage empty = new BufferedImage(GRAY_MODEL, GRAY_MODEL.createCompatibleWritableRaster(width, height), false, null); BufferedImage empty = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY); // when enlarging a layer mask, the new areas need to be white Graphics2D g = empty.createGraphics(); g.setColor(Color.WHITE); g.fillRect(0, 0, width, height); g.dispose(); return empty; } @Override protected void imageRefChanged() { updateFromBWImage(); } @Override public void updateIconImage() { LayerGUI button = getUI(); if(button != null) { // can be null while deserializing button.updateLayerIconImage(this); } } /** * Duplicates this layer mask, and attaches the duplicated mask * to the given layer */ public LayerMask duplicate(Layer master) { BufferedImage maskImageCopy = ImageUtils.copyImage(image); LayerMask d = new LayerMask(comp, maskImageCopy, master, false); d.setTranslation(getTX(), getTY()); return d; } public boolean isLinked() { return linked; } public void setLinked(boolean linked, boolean addToHistory) { this.linked = linked; notifyLayerChangeObservers(); History.addEdit(addToHistory, () -> new LinkLayerMaskEdit(comp, this)); } @Override public TmpDrawingLayer createTmpDrawingLayer(Composite c) { throw new IllegalStateException("tmp layer with masks"); } // @Override // public void mergeTmpDrawingLayerDown() { // updateIconImage(); // } @Override protected void paintLayerOnGraphicsWOTmpLayer(Graphics2D g, boolean firstVisibleLayer, BufferedImage visibleImage) { if (Tools.isShapesDrawing()) { paintDraggedShapesIntoActiveLayer(g, visibleImage, firstVisibleLayer); } else { // the simple case g.drawImage(visibleImage, getTX(), getTY(), null); } } @Override protected void paintDraggedShapesIntoActiveLayer(Graphics2D g, BufferedImage visibleImage, boolean firstVisibleLayer) { g.drawImage(visibleImage, getTX(), getTY(), null); Tools.SHAPES.paintOverLayer(g, comp); } public BufferedImage getTransparencyImage() { if(!parent.isMaskEditing() || !Tools.isShapesDrawing()) { // simple case return transparencyImage; } else { // drawing with the shapes tool while in Ctrl-3 mode // Create a temporary image that shows how the image would look like // if the shapes tool would draw directly into the mask image BufferedImage tmp = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_BYTE_GRAY); Graphics2D tmpG = tmp.createGraphics(); tmpG.drawImage(image, 0, 0, null); Tools.SHAPES.paintOverLayer(tmpG, comp); tmpG.dispose(); // ... and return a transparency image based on it WritableRaster raster = tmp.getRaster(); BufferedImage tmpTransparency = new BufferedImage(TRANSPARENCY_COLOR_MODEL, raster, false, null); return tmpTransparency; } } }