1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.ide.eclipse.gltrace.state.transforms; 18 19 import com.android.ide.eclipse.gltrace.FileUtils; 20 import com.android.ide.eclipse.gltrace.GLEnum; 21 import com.android.ide.eclipse.gltrace.state.GLStringProperty; 22 import com.android.ide.eclipse.gltrace.state.IGLProperty; 23 import com.google.common.io.Files; 24 25 import java.awt.image.BufferedImage; 26 import java.io.File; 27 import java.io.IOException; 28 29 import javax.imageio.ImageIO; 30 31 /** 32 * {@link TexImageTransform} transforms the state to reflect the effect of a 33 * glTexImage2D or glTexSubImage2D GL call. 34 */ 35 public class TexImageTransform implements IStateTransform { 36 private static final String PNG_IMAGE_FORMAT = "PNG"; 37 private static final String TEXTURE_FILE_PREFIX = "tex"; 38 private static final String TEXTURE_FILE_SUFFIX = ".png"; 39 40 private final IGLPropertyAccessor mAccessor; 41 private final File mTextureDataFile; 42 43 private final int mxOffset; 44 private final int myOffset; 45 private final int mWidth; 46 private final int mHeight; 47 48 private String mOldValue; 49 private String mNewValue; 50 private GLEnum mFormat; 51 52 /** 53 * Construct a texture image transformation. 54 * @param accessor accessor to obtain the GL state variable to modify 55 * @param textureData texture data passed in by the call. Could be null. 56 * @param format format of the source texture data 57 * @param xOffset x offset for the source data (used only in glTexSubImage2D) 58 * @param yOffset y offset for the source data (used only in glTexSubImage2D) 59 * @param width width of the texture 60 * @param height height of the texture 61 */ 62 public TexImageTransform(IGLPropertyAccessor accessor, File textureData, GLEnum format, 63 int xOffset, int yOffset, int width, int height) { 64 mAccessor = accessor; 65 mTextureDataFile = textureData; 66 mFormat = format; 67 68 mxOffset = xOffset; 69 myOffset = yOffset; 70 mWidth = width; 71 mHeight = height; 72 } 73 74 @Override 75 public void apply(IGLProperty currentState) { 76 assert mOldValue == null : "Transform cannot be applied multiple times"; //$NON-NLS-1$ 77 78 IGLProperty property = mAccessor.getProperty(currentState); 79 if (!(property instanceof GLStringProperty)) { 80 return; 81 } 82 83 GLStringProperty prop = (GLStringProperty) property; 84 mOldValue = prop.getStringValue(); 85 86 // Applying texture transformations is a heavy weight process. So we perform 87 // it only once and save the result in a temporary file. The property is actually 88 // the path to the file. 89 if (mNewValue == null) { 90 try { 91 if (mOldValue == null) { 92 mNewValue = createTexture(mTextureDataFile, mWidth, mHeight); 93 } else { 94 mNewValue = updateTextureData(mOldValue, mTextureDataFile, mxOffset, myOffset, 95 mWidth, mHeight); 96 } 97 } catch (IOException e) { 98 throw new RuntimeException(e); 99 } 100 } 101 102 prop.setValue(mNewValue); 103 } 104 105 @Override 106 public void revert(IGLProperty state) { 107 if (mOldValue != null) { 108 IGLProperty property = mAccessor.getProperty(state); 109 property.setValue(mOldValue); 110 mOldValue = null; 111 } 112 } 113 114 @Override 115 public IGLProperty getChangedProperty(IGLProperty state) { 116 return mAccessor.getProperty(state); 117 } 118 119 /** 120 * Creates a texture of provided width and height. If the texture data file is provided, 121 * then the texture is initialized with the contents of that file, otherwise an empty 122 * image is created. 123 * @param textureDataFile path to texture data, could be null. 124 * @param width width of texture 125 * @param height height of texture 126 * @return path to cached texture 127 */ 128 private String createTexture(File textureDataFile, int width, int height) throws IOException { 129 File f = FileUtils.createTempFile(TEXTURE_FILE_PREFIX, TEXTURE_FILE_SUFFIX); 130 131 BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR); 132 133 if (textureDataFile != null) { 134 byte[] initialData = Files.toByteArray(textureDataFile); 135 img.getRaster().setDataElements(0, 0, width, height, 136 formatSourceData(initialData, width, height)); 137 } 138 139 ImageIO.write(img, PNG_IMAGE_FORMAT, f); 140 141 return f.getAbsolutePath(); 142 } 143 144 /** 145 * Update part of an existing texture. 146 * @param currentImagePath current texture image. 147 * @param textureDataFile new data to update the current texture with 148 * @param xOffset x offset for the update region 149 * @param yOffset y offset for the update region 150 * @param width width of the update region 151 * @param height height of the update region 152 * @return path to the updated texture 153 */ 154 private String updateTextureData(String currentImagePath, File textureDataFile, 155 int xOffset, int yOffset, int width, int height) throws IOException { 156 assert currentImagePath != null : "Attempt to update a null texture"; 157 158 if (textureDataFile == null) { 159 // Do not perform any updates if we don't have the actual data. 160 return currentImagePath; 161 } 162 163 File f = FileUtils.createTempFile(TEXTURE_FILE_PREFIX, TEXTURE_FILE_SUFFIX); 164 BufferedImage image = null; 165 image = ImageIO.read(new File(currentImagePath)); 166 167 byte[] subImageData = Files.toByteArray(textureDataFile); 168 image.getRaster().setDataElements(xOffset, yOffset, width, height, 169 formatSourceData(subImageData, width, height)); 170 171 ImageIO.write(image, PNG_IMAGE_FORMAT, f); 172 173 return f.getAbsolutePath(); 174 } 175 176 private byte[] formatSourceData(byte[] subImageData, int width, int height) { 177 switch (mFormat) { 178 case GL_RGBA: 179 // no conversions necessary 180 return subImageData; 181 case GL_RGB: 182 return addAlphaChannel(subImageData, width, height); 183 case GL_ALPHA: 184 return addRGBChannels(subImageData, width, height); 185 case GL_LUMINANCE: 186 return createRGBAFromLuminance(subImageData, width, height); 187 case GL_LUMINANCE_ALPHA: 188 return createRGBAFromLuminanceAlpha(subImageData, width, height); 189 default: 190 throw new RuntimeException(); 191 } 192 } 193 194 private byte[] addAlphaChannel(byte[] sourceData, int width, int height) { 195 assert sourceData.length == 3 * width * height; // should have R, G & B channels 196 197 byte[] data = new byte[4 * width * height]; 198 199 for (int src = 0, dst = 0; src < sourceData.length; src += 3, dst += 4) { 200 data[dst + 0] = sourceData[src + 0]; // copy R byte 201 data[dst + 1] = sourceData[src + 1]; // copy G byte 202 data[dst + 2] = sourceData[src + 2]; // copy B byte 203 data[dst + 3] = 1; // add alpha = 1 204 } 205 206 return data; 207 } 208 209 private byte[] addRGBChannels(byte[] sourceData, int width, int height) { 210 assert sourceData.length == width * height; // should have a single alpha channel 211 212 byte[] data = new byte[4 * width * height]; 213 214 for (int src = 0, dst = 0; src < sourceData.length; src++, dst += 4) { 215 data[dst + 0] = data[dst + 1] = data[dst + 2] = 0; // set R = G = B = 0 216 data[dst + 3] = sourceData[src]; // copy over alpha 217 } 218 219 return data; 220 } 221 222 private byte[] createRGBAFromLuminance(byte[] sourceData, int width, int height) { 223 assert sourceData.length == width * height; // should have a single luminance channel 224 225 byte[] data = new byte[4 * width * height]; 226 227 for (int src = 0, dst = 0; src < sourceData.length; src++, dst += 4) { 228 int l = sourceData[src] * 3; 229 if (l > 255) { // clamp to 255 230 l = 255; 231 } 232 233 data[dst + 0] = data[dst + 1] = data[dst + 2] = (byte) l; // set R = G = B = L * 3 234 data[dst + 3] = 1; // set alpha = 1 235 } 236 237 return data; 238 } 239 240 private byte[] createRGBAFromLuminanceAlpha(byte[] sourceData, int width, int height) { 241 assert sourceData.length == 2 * width * height; // should have luminance & alpha channels 242 243 byte[] data = new byte[4 * width * height]; 244 245 for (int src = 0, dst = 0; src < sourceData.length; src += 2, dst += 4) { 246 int l = sourceData[src] * 3; 247 if (l > 255) { // clamp to 255 248 l = 255; 249 } 250 251 data[dst + 0] = data[dst + 1] = data[dst + 2] = (byte) l; // set R = G = B = L * 3 252 data[dst + 3] = sourceData[src + 1]; // copy over alpha 253 } 254 255 return data; 256 } 257 } 258