blob: 37cb7a263d44fe95ffd50b6bfb9a0fa356a1074f [file] [log] [blame]
/*
* 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);
}