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 import com.google.common.primitives.UnsignedBytes; 25 26 import java.awt.image.BufferedImage; 27 import java.io.File; 28 import java.io.IOException; 29 30 import javax.imageio.ImageIO; 31 32 /** 33 * {@link TexImageTransform} transforms the state to reflect the effect of a 34 * glTexImage2D or glTexSubImage2D GL call. 35 */ 36 public class TexImageTransform implements IStateTransform { 37 private static final String PNG_IMAGE_FORMAT = "PNG"; 38 private static final String TEXTURE_FILE_PREFIX = "tex"; 39 private static final String TEXTURE_FILE_SUFFIX = ".png"; 40 41 private final IGLPropertyAccessor mAccessor; 42 private final File mTextureDataFile; 43 44 private final int mxOffset; 45 private final int myOffset; 46 private final int mWidth; 47 private final int mHeight; 48 49 private String mOldValue; 50 private String mNewValue; 51 private GLEnum mFormat; 52 private GLEnum mType; 53 54 /** 55 * Construct a texture image transformation. 56 * @param accessor accessor to obtain the GL state variable to modify 57 * @param textureData texture data passed in by the call. Could be null. 58 * @param format format of the source texture data 59 * @param xOffset x offset for the source data (used only in glTexSubImage2D) 60 * @param yOffset y offset for the source data (used only in glTexSubImage2D) 61 * @param width width of the texture 62 * @param height height of the texture 63 */ 64 public TexImageTransform(IGLPropertyAccessor accessor, File textureData, GLEnum format, 65 GLEnum type, int xOffset, int yOffset, int width, int height) { 66 mAccessor = accessor; 67 mTextureDataFile = textureData; 68 mFormat = format; 69 mType = type; 70 71 mxOffset = xOffset; 72 myOffset = yOffset; 73 mWidth = width; 74 mHeight = height; 75 } 76 77 @Override 78 public void apply(IGLProperty currentState) { 79 assert mOldValue == null : "Transform cannot be applied multiple times"; //$NON-NLS-1$ 80 81 IGLProperty property = mAccessor.getProperty(currentState); 82 if (!(property instanceof GLStringProperty)) { 83 return; 84 } 85 86 GLStringProperty prop = (GLStringProperty) property; 87 mOldValue = prop.getStringValue(); 88 89 // Applying texture transformations is a heavy weight process. So we perform 90 // it only once and save the result in a temporary file. The property is actually 91 // the path to the file. 92 if (mNewValue == null) { 93 try { 94 if (mOldValue == null) { 95 mNewValue = createTexture(mTextureDataFile, mWidth, mHeight); 96 } else { 97 mNewValue = updateTextureData(mOldValue, mTextureDataFile, mxOffset, myOffset, 98 mWidth, mHeight); 99 } 100 } catch (IOException e) { 101 throw new RuntimeException(e); 102 } 103 } 104 105 prop.setValue(mNewValue); 106 } 107 108 @Override 109 public void revert(IGLProperty state) { 110 if (mOldValue != null) { 111 IGLProperty property = mAccessor.getProperty(state); 112 property.setValue(mOldValue); 113 mOldValue = null; 114 } 115 } 116 117 @Override 118 public IGLProperty getChangedProperty(IGLProperty state) { 119 return mAccessor.getProperty(state); 120 } 121 122 /** 123 * Creates a texture of provided width and height. If the texture data file is provided, 124 * then the texture is initialized with the contents of that file, otherwise an empty 125 * image is created. 126 * @param textureDataFile path to texture data, could be null. 127 * @param width width of texture 128 * @param height height of texture 129 * @return path to cached texture 130 */ 131 private String createTexture(File textureDataFile, int width, int height) throws IOException { 132 File f = FileUtils.createTempFile(TEXTURE_FILE_PREFIX, TEXTURE_FILE_SUFFIX); 133 134 BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR); 135 136 if (textureDataFile != null) { 137 byte[] initialData = Files.toByteArray(textureDataFile); 138 img.getRaster().setDataElements(0, 0, width, height, 139 formatSourceData(initialData, width, height)); 140 } 141 142 ImageIO.write(img, PNG_IMAGE_FORMAT, f); 143 144 return f.getAbsolutePath(); 145 } 146 147 /** 148 * Update part of an existing texture. 149 * @param currentImagePath current texture image. 150 * @param textureDataFile new data to update the current texture with 151 * @param xOffset x offset for the update region 152 * @param yOffset y offset for the update region 153 * @param width width of the update region 154 * @param height height of the update region 155 * @return path to the updated texture 156 */ 157 private String updateTextureData(String currentImagePath, File textureDataFile, 158 int xOffset, int yOffset, int width, int height) throws IOException { 159 assert currentImagePath != null : "Attempt to update a null texture"; 160 161 if (textureDataFile == null) { 162 // Do not perform any updates if we don't have the actual data. 163 return currentImagePath; 164 } 165 166 File f = FileUtils.createTempFile(TEXTURE_FILE_PREFIX, TEXTURE_FILE_SUFFIX); 167 BufferedImage image = null; 168 image = ImageIO.read(new File(currentImagePath)); 169 170 byte[] subImageData = Files.toByteArray(textureDataFile); 171 image.getRaster().setDataElements(xOffset, yOffset, width, height, 172 formatSourceData(subImageData, width, height)); 173 ImageIO.write(image, PNG_IMAGE_FORMAT, f); 174 175 return f.getAbsolutePath(); 176 } 177 178 private byte[] formatSourceData(byte[] subImageData, int width, int height) { 179 if (mType != GLEnum.GL_UNSIGNED_BYTE) { 180 subImageData = unpackData(subImageData, mType); 181 } 182 183 switch (mFormat) { 184 case GL_RGBA: 185 // no conversions necessary 186 return subImageData; 187 case GL_RGB: 188 return addAlphaChannel(subImageData, width, height); 189 case GL_ALPHA: 190 return addRGBChannels(subImageData, width, height); 191 case GL_LUMINANCE: 192 return createRGBAFromLuminance(subImageData, width, height); 193 case GL_LUMINANCE_ALPHA: 194 return createRGBAFromLuminanceAlpha(subImageData, width, height); 195 default: 196 throw new RuntimeException(); 197 } 198 } 199 200 private byte[] unpackData(byte[] data, GLEnum type) { 201 switch (type) { 202 case GL_UNSIGNED_BYTE: 203 return data; 204 case GL_UNSIGNED_SHORT_4_4_4_4: 205 return convertShortToUnsigned(data, 0xf000, 12, 0x0f00, 8, 0x00f0, 4, 0x000f, 0, 206 true); 207 case GL_UNSIGNED_SHORT_5_6_5: 208 return convertShortToUnsigned(data, 0xf800, 11, 0x07e0, 5, 0x001f, 0, 0, 0, 209 false); 210 case GL_UNSIGNED_SHORT_5_5_5_1: 211 return convertShortToUnsigned(data, 0xf800, 11, 0x07c0, 6, 0x003e, 1, 0x1, 0, 212 true); 213 default: 214 return data; 215 } 216 } 217 218 private byte[] convertShortToUnsigned(byte[] shortData, 219 int rmask, int rshift, 220 int gmask, int gshift, 221 int bmask, int bshift, 222 int amask, int ashift, 223 boolean includeAlpha) { 224 int numChannels = includeAlpha ? 4 : 3; 225 byte[] unsignedData = new byte[(shortData.length/2) * numChannels]; 226 227 for (int i = 0; i < (shortData.length / 2); i++) { 228 int hi = UnsignedBytes.toInt(shortData[i*2 + 0]); 229 int lo = UnsignedBytes.toInt(shortData[i*2 + 1]); 230 231 int x = hi << 8 | lo; 232 233 int r = (x & rmask) >>> rshift; 234 int g = (x & gmask) >>> gshift; 235 int b = (x & bmask) >>> bshift; 236 int a = (x & amask) >>> ashift; 237 238 unsignedData[i * numChannels + 0] = UnsignedBytes.checkedCast(r); 239 unsignedData[i * numChannels + 1] = UnsignedBytes.checkedCast(g); 240 unsignedData[i * numChannels + 2] = UnsignedBytes.checkedCast(b); 241 242 if (includeAlpha) { 243 unsignedData[i * numChannels + 3] = UnsignedBytes.checkedCast(a); 244 } 245 } 246 247 return unsignedData; 248 } 249 250 private byte[] addAlphaChannel(byte[] sourceData, int width, int height) { 251 assert sourceData.length == 3 * width * height; // should have R, G & B channels 252 253 byte[] data = new byte[4 * width * height]; 254 255 for (int src = 0, dst = 0; src < sourceData.length; src += 3, dst += 4) { 256 data[dst + 0] = sourceData[src + 0]; // copy R byte 257 data[dst + 1] = sourceData[src + 1]; // copy G byte 258 data[dst + 2] = sourceData[src + 2]; // copy B byte 259 data[dst + 3] = 1; // add alpha = 1 260 } 261 262 return data; 263 } 264 265 private byte[] addRGBChannels(byte[] sourceData, int width, int height) { 266 assert sourceData.length == width * height; // should have a single alpha channel 267 268 byte[] data = new byte[4 * width * height]; 269 270 for (int src = 0, dst = 0; src < sourceData.length; src++, dst += 4) { 271 data[dst + 0] = data[dst + 1] = data[dst + 2] = 0; // set R = G = B = 0 272 data[dst + 3] = sourceData[src]; // copy over alpha 273 } 274 275 return data; 276 } 277 278 private byte[] createRGBAFromLuminance(byte[] sourceData, int width, int height) { 279 assert sourceData.length == width * height; // should have a single luminance channel 280 281 byte[] data = new byte[4 * width * height]; 282 283 for (int src = 0, dst = 0; src < sourceData.length; src++, dst += 4) { 284 int l = sourceData[src] * 3; 285 if (l > 255) { // clamp to 255 286 l = 255; 287 } 288 289 data[dst + 0] = data[dst + 1] = data[dst + 2] = (byte) l; // set R = G = B = L * 3 290 data[dst + 3] = 1; // set alpha = 1 291 } 292 293 return data; 294 } 295 296 private byte[] createRGBAFromLuminanceAlpha(byte[] sourceData, int width, int height) { 297 assert sourceData.length == 2 * width * height; // should have luminance & alpha channels 298 299 byte[] data = new byte[4 * width * height]; 300 301 for (int src = 0, dst = 0; src < sourceData.length; src += 2, dst += 4) { 302 int l = sourceData[src] * 3; 303 if (l > 255) { // clamp to 255 304 l = 255; 305 } 306 307 data[dst + 0] = data[dst + 1] = data[dst + 2] = (byte) l; // set R = G = B = L * 3 308 data[dst + 3] = sourceData[src + 1]; // copy over alpha 309 } 310 311 return data; 312 } 313 } 314