gdx-studio
git clone https://git.pyrossh.dev/gdx-studio
An IDE for creating Games using libgdx and Java supported on all platforms Android, iOS, Desktop
src_libs/com/badlogic/gdx/tools/hiero/unicodefont/effects/ShadowEffect.java
| a62d533 | 1 | /******************************************************************************* |
| a62d533 | 2 | * Copyright 2011 See AUTHORS file. |
| a62d533 | 3 | * |
| a62d533 | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| a62d533 | 5 | * you may not use this file except in compliance with the License. |
| a62d533 | 6 | * You may obtain a copy of the License at |
| a62d533 | 7 | * |
| a62d533 | 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| a62d533 | 9 | * |
| a62d533 | 10 | * Unless required by applicable law or agreed to in writing, software |
| a62d533 | 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| a62d533 | 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| a62d533 | 13 | * See the License for the specific language governing permissions and |
| a62d533 | 14 | * limitations under the License. |
| a62d533 | 15 | ******************************************************************************/ |
| a62d533 | 16 | |
| a62d533 | 17 | package com.badlogic.gdx.tools.hiero.unicodefont.effects; |
| a62d533 | 18 | |
| a62d533 | 19 | import java.awt.AlphaComposite; |
| a62d533 | 20 | import java.awt.Color; |
| a62d533 | 21 | import java.awt.Composite; |
| a62d533 | 22 | import java.awt.Graphics2D; |
| a62d533 | 23 | import java.awt.RenderingHints; |
| a62d533 | 24 | import java.awt.image.BufferedImage; |
| a62d533 | 25 | import java.awt.image.ConvolveOp; |
| a62d533 | 26 | import java.awt.image.Kernel; |
| a62d533 | 27 | import java.util.ArrayList; |
| a62d533 | 28 | import java.util.Iterator; |
| a62d533 | 29 | import java.util.List; |
| a62d533 | 30 | |
| a62d533 | 31 | import com.badlogic.gdx.tools.hiero.unicodefont.Glyph; |
| a62d533 | 32 | import com.badlogic.gdx.tools.hiero.unicodefont.UnicodeFont; |
| a62d533 | 33 | |
| a62d533 | 34 | /** @author Nathan Sweet */ |
| a62d533 | 35 | public class ShadowEffect implements ConfigurableEffect {
|
| a62d533 | 36 | /** The numberof kernels to apply */ |
| a62d533 | 37 | public static final int NUM_KERNELS = 16; |
| a62d533 | 38 | /** The blur kernels applied across the effect */ |
| a62d533 | 39 | public static final float[][] GAUSSIAN_BLUR_KERNELS = generateGaussianBlurKernels(NUM_KERNELS); |
| a62d533 | 40 | |
| a62d533 | 41 | private Color color = Color.black; |
| a62d533 | 42 | private float opacity = 0.6f; |
| a62d533 | 43 | private float xDistance = 2, yDistance = 2; |
| a62d533 | 44 | private int blurKernelSize = 0; |
| a62d533 | 45 | private int blurPasses = 1; |
| a62d533 | 46 | |
| a62d533 | 47 | public ShadowEffect () {
|
| a62d533 | 48 | } |
| a62d533 | 49 | |
| a62d533 | 50 | public ShadowEffect (Color color, int xDistance, int yDistance, float opacity) {
|
| a62d533 | 51 | this.color = color; |
| a62d533 | 52 | this.xDistance = xDistance; |
| a62d533 | 53 | this.yDistance = yDistance; |
| a62d533 | 54 | this.opacity = opacity; |
| a62d533 | 55 | } |
| a62d533 | 56 | |
| a62d533 | 57 | public void draw (BufferedImage image, Graphics2D g, UnicodeFont unicodeFont, Glyph glyph) {
|
| a62d533 | 58 | g = (Graphics2D)g.create(); |
| a62d533 | 59 | g.translate(xDistance, yDistance); |
| a62d533 | 60 | g.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), Math.round(opacity * 255))); |
| a62d533 | 61 | g.fill(glyph.getShape()); |
| a62d533 | 62 | |
| a62d533 | 63 | // Also shadow the outline, if one exists. |
| a62d533 | 64 | for (Iterator iter = unicodeFont.getEffects().iterator(); iter.hasNext();) {
|
| a62d533 | 65 | Effect effect = (Effect)iter.next(); |
| a62d533 | 66 | if (effect instanceof OutlineEffect) {
|
| a62d533 | 67 | Composite composite = g.getComposite(); |
| a62d533 | 68 | g.setComposite(AlphaComposite.Src); // Prevent shadow and outline shadow alpha from combining. |
| a62d533 | 69 | |
| a62d533 | 70 | g.setStroke(((OutlineEffect)effect).getStroke()); |
| a62d533 | 71 | g.draw(glyph.getShape()); |
| a62d533 | 72 | |
| a62d533 | 73 | g.setComposite(composite); |
| a62d533 | 74 | break; |
| a62d533 | 75 | } |
| a62d533 | 76 | } |
| a62d533 | 77 | |
| a62d533 | 78 | g.dispose(); |
| a62d533 | 79 | if (blurKernelSize > 1 && blurKernelSize < NUM_KERNELS && blurPasses > 0) blur(image); |
| a62d533 | 80 | } |
| a62d533 | 81 | |
| a62d533 | 82 | private void blur (BufferedImage image) {
|
| a62d533 | 83 | float[] matrix = GAUSSIAN_BLUR_KERNELS[blurKernelSize - 1]; |
| a62d533 | 84 | Kernel gaussianBlur1 = new Kernel(matrix.length, 1, matrix); |
| a62d533 | 85 | Kernel gaussianBlur2 = new Kernel(1, matrix.length, matrix); |
| a62d533 | 86 | RenderingHints hints = new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED); |
| a62d533 | 87 | ConvolveOp gaussianOp1 = new ConvolveOp(gaussianBlur1, ConvolveOp.EDGE_NO_OP, hints); |
| a62d533 | 88 | ConvolveOp gaussianOp2 = new ConvolveOp(gaussianBlur2, ConvolveOp.EDGE_NO_OP, hints); |
| a62d533 | 89 | BufferedImage scratchImage = EffectUtil.getScratchImage(); |
| a62d533 | 90 | for (int i = 0; i < blurPasses; i++) {
|
| a62d533 | 91 | gaussianOp1.filter(image, scratchImage); |
| a62d533 | 92 | gaussianOp2.filter(scratchImage, image); |
| a62d533 | 93 | } |
| a62d533 | 94 | } |
| a62d533 | 95 | |
| a62d533 | 96 | public Color getColor () {
|
| a62d533 | 97 | return color; |
| a62d533 | 98 | } |
| a62d533 | 99 | |
| a62d533 | 100 | public void setColor (Color color) {
|
| a62d533 | 101 | this.color = color; |
| a62d533 | 102 | } |
| a62d533 | 103 | |
| a62d533 | 104 | public float getXDistance () {
|
| a62d533 | 105 | return xDistance; |
| a62d533 | 106 | } |
| a62d533 | 107 | |
| a62d533 | 108 | /** Sets the pixels to offset the shadow on the x axis. The glyphs will need padding so the shadow doesn't get clipped. */ |
| a62d533 | 109 | public void setXDistance (float distance) {
|
| a62d533 | 110 | xDistance = distance; |
| a62d533 | 111 | } |
| a62d533 | 112 | |
| a62d533 | 113 | public float getYDistance () {
|
| a62d533 | 114 | return yDistance; |
| a62d533 | 115 | } |
| a62d533 | 116 | |
| a62d533 | 117 | /** Sets the pixels to offset the shadow on the y axis. The glyphs will need padding so the shadow doesn't get clipped. */ |
| a62d533 | 118 | public void setYDistance (float distance) {
|
| a62d533 | 119 | yDistance = distance; |
| a62d533 | 120 | } |
| a62d533 | 121 | |
| a62d533 | 122 | public int getBlurKernelSize () {
|
| a62d533 | 123 | return blurKernelSize; |
| a62d533 | 124 | } |
| a62d533 | 125 | |
| a62d533 | 126 | /** Sets how many neighboring pixels are used to blur the shadow. Set to 0 for no blur. */ |
| a62d533 | 127 | public void setBlurKernelSize (int blurKernelSize) {
|
| a62d533 | 128 | this.blurKernelSize = blurKernelSize; |
| a62d533 | 129 | } |
| a62d533 | 130 | |
| a62d533 | 131 | public int getBlurPasses () {
|
| a62d533 | 132 | return blurPasses; |
| a62d533 | 133 | } |
| a62d533 | 134 | |
| a62d533 | 135 | /** Sets the number of times to apply a blur to the shadow. Set to 0 for no blur. */ |
| a62d533 | 136 | public void setBlurPasses (int blurPasses) {
|
| a62d533 | 137 | this.blurPasses = blurPasses; |
| a62d533 | 138 | } |
| a62d533 | 139 | |
| a62d533 | 140 | public float getOpacity () {
|
| a62d533 | 141 | return opacity; |
| a62d533 | 142 | } |
| a62d533 | 143 | |
| a62d533 | 144 | public void setOpacity (float opacity) {
|
| a62d533 | 145 | this.opacity = opacity; |
| a62d533 | 146 | } |
| a62d533 | 147 | |
| a62d533 | 148 | public String toString () {
|
| a62d533 | 149 | return "Shadow"; |
| a62d533 | 150 | } |
| a62d533 | 151 | |
| a62d533 | 152 | public List getValues () {
|
| a62d533 | 153 | List values = new ArrayList(); |
| a62d533 | 154 | values.add(EffectUtil.colorValue("Color", color));
|
| a62d533 | 155 | values.add(EffectUtil.floatValue("Opacity", opacity, 0, 1, "This setting sets the translucency of the shadow."));
|
| a62d533 | 156 | values.add(EffectUtil.floatValue("X distance", xDistance, -99, 99, "This setting is the amount of pixels to offset the "
|
| a62d533 | 157 | + "shadow on the x axis. The glyphs will need padding so the shadow doesn't get clipped.")); |
| a62d533 | 158 | values.add(EffectUtil.floatValue("Y distance", yDistance, -99, 99, "This setting is the amount of pixels to offset the "
|
| a62d533 | 159 | + "shadow on the y axis. The glyphs will need padding so the shadow doesn't get clipped.")); |
| a62d533 | 160 | |
| a62d533 | 161 | List options = new ArrayList(); |
| a62d533 | 162 | options.add(new String[] {"None", "0"});
|
| a62d533 | 163 | for (int i = 2; i < NUM_KERNELS; i++) |
| a62d533 | 164 | options.add(new String[] {String.valueOf(i)});
|
| a62d533 | 165 | String[][] optionsArray = (String[][])options.toArray(new String[options.size()][]); |
| a62d533 | 166 | values.add(EffectUtil.optionValue("Blur kernel size", String.valueOf(blurKernelSize), optionsArray,
|
| a62d533 | 167 | "This setting controls how many neighboring pixels are used to blur the shadow. Set to \"None\" for no blur.")); |
| a62d533 | 168 | |
| a62d533 | 169 | values.add(EffectUtil.intValue("Blur passes", blurPasses,
|
| a62d533 | 170 | "The setting is the number of times to apply a blur to the shadow. Set to \"0\" for no blur.")); |
| a62d533 | 171 | return values; |
| a62d533 | 172 | } |
| a62d533 | 173 | |
| a62d533 | 174 | public void setValues (List values) {
|
| a62d533 | 175 | for (Iterator iter = values.iterator(); iter.hasNext();) {
|
| a62d533 | 176 | Value value = (Value)iter.next(); |
| a62d533 | 177 | if (value.getName().equals("Color")) {
|
| a62d533 | 178 | color = (Color)value.getObject(); |
| a62d533 | 179 | } else if (value.getName().equals("Opacity")) {
|
| a62d533 | 180 | opacity = ((Float)value.getObject()).floatValue(); |
| a62d533 | 181 | } else if (value.getName().equals("X distance")) {
|
| a62d533 | 182 | xDistance = ((Float)value.getObject()).floatValue(); |
| a62d533 | 183 | } else if (value.getName().equals("Y distance")) {
|
| a62d533 | 184 | yDistance = ((Float)value.getObject()).floatValue(); |
| a62d533 | 185 | } else if (value.getName().equals("Blur kernel size")) {
|
| a62d533 | 186 | blurKernelSize = Integer.parseInt((String)value.getObject()); |
| a62d533 | 187 | } else if (value.getName().equals("Blur passes")) {
|
| a62d533 | 188 | blurPasses = ((Integer)value.getObject()).intValue(); |
| a62d533 | 189 | } |
| a62d533 | 190 | } |
| a62d533 | 191 | } |
| a62d533 | 192 | |
| a62d533 | 193 | /** Generate the blur kernels which will be repeatedly applied when blurring images |
| a62d533 | 194 | * |
| a62d533 | 195 | * @param level The number of kernels to generate |
| a62d533 | 196 | * @return The kernels generated */ |
| a62d533 | 197 | private static float[][] generateGaussianBlurKernels (int level) {
|
| a62d533 | 198 | float[][] pascalsTriangle = generatePascalsTriangle(level); |
| a62d533 | 199 | float[][] gaussianTriangle = new float[pascalsTriangle.length][]; |
| a62d533 | 200 | for (int i = 0; i < gaussianTriangle.length; i++) {
|
| a62d533 | 201 | float total = 0.0f; |
| a62d533 | 202 | gaussianTriangle[i] = new float[pascalsTriangle[i].length]; |
| a62d533 | 203 | for (int j = 0; j < pascalsTriangle[i].length; j++) |
| a62d533 | 204 | total += pascalsTriangle[i][j]; |
| a62d533 | 205 | float coefficient = 1 / total; |
| a62d533 | 206 | for (int j = 0; j < pascalsTriangle[i].length; j++) |
| a62d533 | 207 | gaussianTriangle[i][j] = coefficient * pascalsTriangle[i][j]; |
| a62d533 | 208 | } |
| a62d533 | 209 | return gaussianTriangle; |
| a62d533 | 210 | } |
| a62d533 | 211 | |
| a62d533 | 212 | /** Generate Pascal's triangle |
| a62d533 | 213 | * |
| a62d533 | 214 | * @param level The level of the triangle to generate |
| a62d533 | 215 | * @return The Pascal's triangle kernel */ |
| a62d533 | 216 | private static float[][] generatePascalsTriangle (int level) {
|
| a62d533 | 217 | if (level < 2) level = 2; |
| a62d533 | 218 | float[][] triangle = new float[level][]; |
| a62d533 | 219 | triangle[0] = new float[1]; |
| a62d533 | 220 | triangle[1] = new float[2]; |
| a62d533 | 221 | triangle[0][0] = 1.0f; |
| a62d533 | 222 | triangle[1][0] = 1.0f; |
| a62d533 | 223 | triangle[1][1] = 1.0f; |
| a62d533 | 224 | for (int i = 2; i < level; i++) {
|
| a62d533 | 225 | triangle[i] = new float[i + 1]; |
| a62d533 | 226 | triangle[i][0] = 1.0f; |
| a62d533 | 227 | triangle[i][i] = 1.0f; |
| a62d533 | 228 | for (int j = 1; j < triangle[i].length - 1; j++) |
| a62d533 | 229 | triangle[i][j] = triangle[i - 1][j - 1] + triangle[i - 1][j]; |
| a62d533 | 230 | } |
| a62d533 | 231 | return triangle; |
| a62d533 | 232 | } |
| a62d533 | 233 | } |