| /* |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| #include "TexWrapper.h" |
| #include "glError.h" |
| |
| #include <fcntl.h> |
| #include <malloc.h> |
| #include <png.h> |
| |
| #include <android-base/logging.h> |
| |
| |
| /* Create an new empty GL texture that will be filled later */ |
| TexWrapper::TexWrapper() { |
| GLuint textureId; |
| glGenTextures(1, &textureId); |
| if (textureId <= 0) { |
| LOG(ERROR) << "Didn't get a texture handle allocated: " << getEGLError(); |
| } else { |
| // Store the basic texture properties |
| id = textureId; |
| w = 0; |
| h = 0; |
| } |
| } |
| |
| |
| /* Wrap a texture that already allocated. The wrapper takes ownership. */ |
| TexWrapper::TexWrapper(GLuint textureId, unsigned width, unsigned height) { |
| // Store the basic texture properties |
| id = textureId; |
| w = width; |
| h = height; |
| } |
| |
| |
| TexWrapper::~TexWrapper() { |
| // Give the texture ID back |
| if (id > 0) { |
| glDeleteTextures(1, &id); |
| } |
| id = -1; |
| } |
| |
| |
| /* Factory to build TexWrapper objects from a given PNG file */ |
| TexWrapper* createTextureFromPng(const char * filename) |
| { |
| // Open the PNG file |
| FILE *inputFile = fopen(filename, "rb"); |
| if (inputFile == 0) |
| { |
| perror(filename); |
| return nullptr; |
| } |
| |
| // Read the file header and validate that it is a PNG |
| static const int kSigSize = 8; |
| png_byte header[kSigSize] = {0}; |
| fread(header, 1, kSigSize, inputFile); |
| if (png_sig_cmp(header, 0, kSigSize)) { |
| printf("%s is not a PNG.\n", filename); |
| fclose(inputFile); |
| return nullptr; |
| } |
| |
| // Set up our control structure |
| png_structp pngControl = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); |
| if (!pngControl) |
| { |
| printf("png_create_read_struct failed.\n"); |
| fclose(inputFile); |
| return nullptr; |
| } |
| |
| // Set up our image info structure |
| png_infop pngInfo = png_create_info_struct(pngControl); |
| if (!pngInfo) |
| { |
| printf("error: png_create_info_struct returned 0.\n"); |
| png_destroy_read_struct(&pngControl, nullptr, nullptr); |
| fclose(inputFile); |
| return nullptr; |
| } |
| |
| // Install an error handler |
| if (setjmp(png_jmpbuf(pngControl))) { |
| printf("libpng reported an error\n"); |
| png_destroy_read_struct(&pngControl, &pngInfo, nullptr); |
| fclose(inputFile); |
| return nullptr; |
| } |
| |
| // Set up the png reader and fetch the remaining bits of the header |
| png_init_io(pngControl, inputFile); |
| png_set_sig_bytes(pngControl, kSigSize); |
| png_read_info(pngControl, pngInfo); |
| |
| // Get basic information about the PNG we're reading |
| int bitDepth; |
| int colorFormat; |
| png_uint_32 width; |
| png_uint_32 height; |
| png_get_IHDR(pngControl, pngInfo, |
| &width, &height, |
| &bitDepth, &colorFormat, |
| NULL, NULL, NULL); |
| |
| GLint format; |
| switch(colorFormat) |
| { |
| case PNG_COLOR_TYPE_RGB: |
| format = GL_RGB; |
| break; |
| case PNG_COLOR_TYPE_RGB_ALPHA: |
| format = GL_RGBA; |
| break; |
| default: |
| printf("%s: Unknown libpng color format %d.\n", filename, colorFormat); |
| return nullptr; |
| } |
| |
| // Refresh the values in the png info struct in case any transformation shave been applied. |
| png_read_update_info(pngControl, pngInfo); |
| int stride = png_get_rowbytes(pngControl, pngInfo); |
| stride += 3 - ((stride-1) % 4); // glTexImage2d requires rows to be 4-byte aligned |
| |
| // Allocate storage for the pixel data |
| png_byte * buffer = (png_byte*)malloc(stride * height); |
| if (buffer == NULL) |
| { |
| printf("error: could not allocate memory for PNG image data\n"); |
| png_destroy_read_struct(&pngControl, &pngInfo, nullptr); |
| fclose(inputFile); |
| return nullptr; |
| } |
| |
| // libpng needs an array of pointers into the image data for each row |
| png_byte ** rowPointers = (png_byte**)malloc(height * sizeof(png_byte*)); |
| if (rowPointers == NULL) |
| { |
| printf("Failed to allocate temporary row pointers\n"); |
| png_destroy_read_struct(&pngControl, &pngInfo, nullptr); |
| free(buffer); |
| fclose(inputFile); |
| return nullptr; |
| } |
| for (unsigned int r = 0; r < height; r++) |
| { |
| rowPointers[r] = buffer + r*stride; |
| } |
| |
| |
| // Read in the actual image bytes |
| png_read_image(pngControl, rowPointers); |
| png_read_end(pngControl, nullptr); |
| |
| |
| // Set up the OpenGL texture to contain this image |
| GLuint textureId; |
| glGenTextures(1, &textureId); |
| glBindTexture(GL_TEXTURE_2D, textureId); |
| |
| // Send the image data to GL |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer); |
| |
| // Initialize the sampling properties (it seems the sample may not work if this isn't done) |
| // The user of this texture may very well want to set their own filtering, but we're going |
| // to pay the (minor) price of setting this up for them to avoid the dreaded "black image" if |
| // they forget. |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
| |
| // clean up |
| png_destroy_read_struct(&pngControl, &pngInfo, nullptr); |
| free(buffer); |
| free(rowPointers); |
| fclose(inputFile); |
| |
| glBindTexture(GL_TEXTURE_2D, 0); |
| |
| |
| // Return the texture |
| return new TexWrapper(textureId, width, height); |
| } |