1/*
2 * Copyright (C) 2017 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#include "TexWrapper.h"
17#include "glError.h"
18
19#include "log/log.h"
20
21#include <fcntl.h>
22#include <malloc.h>
23#include <png.h>
24
25
26/* Create an new empty GL texture that will be filled later */
27TexWrapper::TexWrapper() {
28    GLuint textureId;
29    glGenTextures(1, &textureId);
30    if (textureId <= 0) {
31        ALOGE("Didn't get a texture handle allocated: %s", getEGLError());
32    } else {
33        // Store the basic texture properties
34        id = textureId;
35        w  = 0;
36        h  = 0;
37    }
38}
39
40
41/* Wrap a texture that already allocated.  The wrapper takes ownership. */
42TexWrapper::TexWrapper(GLuint textureId, unsigned width, unsigned height) {
43    // Store the basic texture properties
44    id = textureId;
45    w  = width;
46    h  = height;
47}
48
49
50TexWrapper::~TexWrapper() {
51    // Give the texture ID back
52    if (id > 0) {
53        glDeleteTextures(1, &id);
54    }
55    id = -1;
56}
57
58
59/* Factory to build TexWrapper objects from a given PNG file */
60TexWrapper* createTextureFromPng(const char * filename)
61{
62    // Open the PNG file
63    FILE *inputFile = fopen(filename, "rb");
64    if (inputFile == 0)
65    {
66        perror(filename);
67        return nullptr;
68    }
69
70    // Read the file header and validate that it is a PNG
71    static const int kSigSize = 8;
72    png_byte header[kSigSize] = {0};
73    fread(header, 1, kSigSize, inputFile);
74    if (png_sig_cmp(header, 0, kSigSize)) {
75        printf("%s is not a PNG.\n", filename);
76        fclose(inputFile);
77        return nullptr;
78    }
79
80    // Set up our control structure
81    png_structp pngControl = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
82    if (!pngControl)
83    {
84        printf("png_create_read_struct failed.\n");
85        fclose(inputFile);
86        return nullptr;
87    }
88
89    // Set up our image info structure
90    png_infop pngInfo = png_create_info_struct(pngControl);
91    if (!pngInfo)
92    {
93        printf("error: png_create_info_struct returned 0.\n");
94        png_destroy_read_struct(&pngControl, nullptr, nullptr);
95        fclose(inputFile);
96        return nullptr;
97    }
98
99    // Install an error handler
100    if (setjmp(png_jmpbuf(pngControl))) {
101        printf("libpng reported an error\n");
102        png_destroy_read_struct(&pngControl, &pngInfo, nullptr);
103        fclose(inputFile);
104        return nullptr;
105    }
106
107    // Set up the png reader and fetch the remaining bits of the header
108    png_init_io(pngControl, inputFile);
109    png_set_sig_bytes(pngControl, kSigSize);
110    png_read_info(pngControl, pngInfo);
111
112    // Get basic information about the PNG we're reading
113    int bitDepth;
114    int colorFormat;
115    png_uint_32 width;
116    png_uint_32 height;
117    png_get_IHDR(pngControl, pngInfo,
118                 &width, &height,
119                 &bitDepth, &colorFormat,
120                 NULL, NULL, NULL);
121
122    GLint format;
123    switch(colorFormat)
124    {
125        case PNG_COLOR_TYPE_RGB:
126            format = GL_RGB;
127            break;
128        case PNG_COLOR_TYPE_RGB_ALPHA:
129            format = GL_RGBA;
130            break;
131        default:
132            printf("%s: Unknown libpng color format %d.\n", filename, colorFormat);
133            return nullptr;
134    }
135
136    // Refresh the values in the png info struct in case any transformation shave been applied.
137    png_read_update_info(pngControl, pngInfo);
138    int stride = png_get_rowbytes(pngControl, pngInfo);
139    stride += 3 - ((stride-1) % 4);   // glTexImage2d requires rows to be 4-byte aligned
140
141    // Allocate storage for the pixel data
142    png_byte * buffer = (png_byte*)malloc(stride * height);
143    if (buffer == NULL)
144    {
145        printf("error: could not allocate memory for PNG image data\n");
146        png_destroy_read_struct(&pngControl, &pngInfo, nullptr);
147        fclose(inputFile);
148        return nullptr;
149    }
150
151    // libpng needs an array of pointers into the image data for each row
152    png_byte ** rowPointers = (png_byte**)malloc(height * sizeof(png_byte*));
153    if (rowPointers == NULL)
154    {
155        printf("Failed to allocate temporary row pointers\n");
156        png_destroy_read_struct(&pngControl, &pngInfo, nullptr);
157        free(buffer);
158        fclose(inputFile);
159        return nullptr;
160    }
161    for (unsigned int r = 0; r < height; r++)
162    {
163        rowPointers[r] = buffer + r*stride;
164    }
165
166
167    // Read in the actual image bytes
168    png_read_image(pngControl, rowPointers);
169    png_read_end(pngControl, nullptr);
170
171
172    // Set up the OpenGL texture to contain this image
173    GLuint textureId;
174    glGenTextures(1, &textureId);
175    glBindTexture(GL_TEXTURE_2D, textureId);
176
177    // Send the image data to GL
178    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
179
180    // Initialize the sampling properties (it seems the sample may not work if this isn't done)
181    // The user of this texture may very well want to set their own filtering, but we're going
182    // to pay the (minor) price of setting this up for them to avoid the dreaded "black image" if
183    // they forget.
184    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
185    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
186
187    // clean up
188    png_destroy_read_struct(&pngControl, &pngInfo, nullptr);
189    free(buffer);
190    free(rowPointers);
191    fclose(inputFile);
192
193    glBindTexture(GL_TEXTURE_2D, 0);
194
195
196    // Return the texture
197    return new TexWrapper(textureId, width, height);
198}
199