Home | History | Annotate | Download | only in transforms
      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