PNG图片数据解析

PNG是一种非常流行的图片格式,它不仅支持透明效果,而且图片数据经过了压缩处理,所以广泛用于web等应用。

PNG的文件格式:

  PNG文件中的数据,总是以一个固定的8个字节开头:

    (图片来自http://blog.csdn.net/bisword/article/details/2777121

  除此之外,PNG的其他数据都是以数据块的方式组织,它们被分为标准数据块和辅助数据块,其中的辅助数据块是可选的。关键数据块包含我们必须的图片信息,我们之后要重点解析的也是关键数据块。

  

  (图片来自http://blog.csdn.net/bisword/article/details/2777121

每种数据块的结构:

  

    (图片来自http://blog.csdn.net/bisword/article/details/2777121

  Length:该数据块的中Chunk Data的长度;

  Chunk Type Code:数据类型,就是指上面提到的IHDR,IEND等;

  Chunk Data:数据区域,如果是IDAT,就表示存储的还未解压的图片数据;

  CRC:循环冗余效验码;

 

具体实现:(实现中没有处理png数据中变形的情况,部分头中的宏定义来自libpng,实例不具备实用性,仅作参考)

头文件:

  1 #ifndef __PNG__
  2 #define __PNG__
  3 
  4 #include <stdio.h>
  5 #include <stdlib.h>
  6 #include <math.h>
  7 #include "zlib/zlib.h"
  8 
  9 /**
 10  * 类型标志
 11  */
 12 #define PNG_FLAG_HEX "89504E470D0A1A0A"
 13 
 14 /**
 15  * 数据块类型
 16  */
 17 #define DATA_CHUNK_TYPE_IHDR "IHDR"
 18 #define DATA_CHUNK_TYPE_IDAT "IDAT"
 19 #define DATA_CHUNK_TYPE_IEND "IEND"
 20 #define DATA_CHUNK_TYPE_tEXt "tEXt"
 21 #define DATA_CHUNK_TYPE_iTXt "iTXt"
 22 
 23 /**
 24  * 过滤方式
 25  */
 26 #define DATA_FILTER_TYPE_DEFAULT 0
 27 #define DATA_FILTER_TYPE_ADD_ROW 1
 28 #define DATA_FILTER_TYPE_ADD_UP  2
 29 #define DATA_FILTER_TYPE_AVERGE  3
 30 #define DATA_FILTER_TYPE_PAETH   4
 31 
 32 /* color type masks */
 33 #define PNG_COLOR_MASK_PALETTE    1
 34 #define PNG_COLOR_MASK_COLOR      2
 35 #define PNG_COLOR_MASK_ALPHA      4
 36 
 37 /* color types.  Note that not all combinations are legal */
 38 #define PNG_COLOR_TYPE_GRAY           0
 39 #define PNG_COLOR_TYPE_PALETTE    (PNG_COLOR_MASK_COLOR | PNG_COLOR_MASK_PALETTE)
 40 #define PNG_COLOR_TYPE_RGB        (PNG_COLOR_MASK_COLOR)
 41 #define PNG_COLOR_TYPE_RGB_ALPHA  (PNG_COLOR_MASK_COLOR | PNG_COLOR_MASK_ALPHA)
 42 #define PNG_COLOR_TYPE_GRAY_ALPHA (PNG_COLOR_MASK_ALPHA)
 43 
 44 #define RGB_USE_ALPHA(vr, vg, vb, va) \
 45     (unsigned)(((unsigned)((unsigned char)(vr) * ((unsigned char)(va) + 1)) >> 8) | \
 46     ((unsigned)((unsigned char)(vg) * ((unsigned char)(va) + 1) >> 8) << 8) | \
 47     ((unsigned)((unsigned char)(vb) * ((unsigned char)(va) + 1) >> 8) << 16) | \
 48     ((unsigned)(unsigned char)(va) << 24))
 49 
 50 /**
 51  * 一次解压图片数据的限制
 52  */
 53 #define DECOMPRESSION_MAX_BYTES 8192
 54 
 55 /**
 56  * 数据块信息
 57  */
 58 typedef struct _DataChunkHeader 
 59 {
 60     // 数据长度
 61     unsigned char length[4];
 62     // 数据类型
 63     unsigned char type[4];
 64 } DataChunkHeader;
 65 
 66 /**
 67  * IHDR数据
 68  */
 69 typedef struct _IDHRData
 70 {
 71     unsigned char width[4];
 72     unsigned char height[4];
 73     unsigned char bitDepth[1];
 74     unsigned char colorType[1];
 75     unsigned char compressionMethod[1];
 76     unsigned char filterMethod[1];
 77     unsigned char interlaceMethod[1];
 78 } IDHRData;
 79 
 80 /**
 81  * PNG图片类
 82  */
 83 class PNG
 84 {
 85 public:
 86     PNG();
 87     PNG(const char* filePath);
 88 
 89     ~PNG();
 90 
 91     int getWindth();
 92     int getHeight();
 93 
 94     /**
 95      * 获取图片宽度
 96      */
 97     unsigned char* getImageData();
 98 
 99 private:
100     int m_width;
101     int m_height;
102 
103     unsigned char m_bitDepth;
104     unsigned char m_colorType;
105     unsigned char m_compressionMethod;
106     unsigned char m_filterMethod;
107     unsigned char m_interlaceMethod;
108     unsigned char m_chanels;
109 
110     unsigned char* m_imageData;
111 
112     /**
113      * 从文件加载图片数据
114      */
115     bool loadImageDataFromFile(const char* filePath);
116 
117     /**
118      * 解析数值
119      */
120     int parseNumber(const unsigned char* data, int len);
121 
122     /**
123      * 解压数据
124      */
125     int decompressData(z_stream* zStream, unsigned char* data, int dataLen, int leftLen, FILE *pFile);
126 
127     /**
128      * 生成图片数据
129      */
130     void generateImageData(unsigned char* data, unsigned long dataLen);
131 
132     /**
133      * 默认的过滤方式
134      */
135     void defaultFilterType(unsigned char* pImageData, unsigned char* pRowData, int rowBytes);
136 
137     /**
138      * 当前行相加的过滤方式
139      */
140     void addCurrentRowFilterType(unsigned char* pImageData, unsigned char* pRowData, int rowBytes);
141 
142     /**
143      * 前一行相加的过滤方式
144      */
145     void addUpRowFilterType(unsigned char* pImageData, unsigned char* pRowData, int rowBytes);
146 
147     /**
148      * 平均的过滤方式
149      */
150     void avergeFilterType(unsigned char* pImageData, unsigned char* pRowData, int rowBytes);
151 
152     /**
153      * paeth的过滤方式
154      */
155     void paethFilterType(unsigned char* pImageData, unsigned char* pRowData, int rowBytes);
156 
157     /**
158      * 解析IHDR数据
159      */
160     void parseIHDRData(DataChunkHeader& dataChunkHeader, FILE* pFile);
161 
162     /**
163      * 解析IDAT数据
164      */
165     void parseIDATData(DataChunkHeader& dataChunkHeader, FILE* pFile);
166 
167     /**
168      * 解析IEND数据
169      */
170     void parseIENDData(DataChunkHeader& dataChunkHeader, FILE *pFile);
171 
172     /**
173      * 解析其他数据
174      */
175     void parseCommonData(DataChunkHeader& dataChunkHeader, FILE *pFile);
176 };
177 
178 #endif

cpp文件:

  1 #include "png.h"
  2 #include "utils/cUtil.h"
  3 #include <stdlib.h>
  4 
  5 #include <windows.h>
  6 
  7 /**
  8  * 默认构造函数
  9  */
 10 PNG::PNG()
 11 {
 12     this->m_width = 0;
 13     this->m_height = 0;
 14 
 15     this->m_imageData = 0;
 16 }
 17 
 18 /**
 19  * 构造函数
 20  * @param filePath 图片路径
 21  */
 22 PNG::PNG(const char *filePath)
 23 {
 24     this->m_width = 0;
 25     this->m_height = 0;
 26 
 27     this->loadImageDataFromFile(filePath);
 28 }
 29 
 30 /**
 31  * 析构函数
 32  */
 33 PNG::~PNG()
 34 {
 35 
 36 }
 37 
 38 /**
 39  * 从文件加载图片数据
 40  */
 41 bool PNG::loadImageDataFromFile(const char* filePath)
 42 {
 43     FILE* pFile = fopen(filePath, "rb");
 44     if (!pFile)
 45         return false;
 46 
 47     // 解析PNG标志
 48     char flag[8];
 49     char hexFlag[17];
 50     fread(flag, 1, 8, pFile);
 51     toHexStr(flag, 8, hexFlag);
 52     if (strcmp(hexFlag, PNG_FLAG_HEX) != 0)
 53         return false;
 54 
 55     // 解析图片数据
 56     DataChunkHeader dataChunkHeader;
 57     char dataChunkHeaderType[5];
 58     do {
 59         fread(&dataChunkHeader, 1, sizeof(DataChunkHeader), pFile);
 60 
 61         memcpy(dataChunkHeaderType, dataChunkHeader.type, 4);
 62         dataChunkHeaderType[4] = '\0';
 63 
 64         // IHDR
 65         if ( strcmp(dataChunkHeaderType, DATA_CHUNK_TYPE_IHDR) == 0 ) {
 66             this->parseIHDRData(dataChunkHeader, pFile);
 67         }
 68         // IDAT
 69         else if ( strcmp(dataChunkHeaderType, DATA_CHUNK_TYPE_IDAT) == 0 ) {
 70             this->parseIDATData(dataChunkHeader, pFile);
 71         }
 72         // IEND
 73         else if ( strcmp(dataChunkHeaderType, DATA_CHUNK_TYPE_IEND) == 0 ) {
 74             this->parseIENDData(dataChunkHeader, pFile);
 75         }
 76         // 其他数据
 77         else {
 78             this->parseCommonData(dataChunkHeader, pFile);
 79         }
 80     } while( strcmp(dataChunkHeaderType, DATA_CHUNK_TYPE_IEND) != 0 );
 81 
 82     int i = 1;
 83 
 84     return true;
 85 }
 86 
 87 /**
 88  * 解析数值
 89  */
 90 int PNG::parseNumber(const unsigned char* data, int len)
 91 {
 92     char localNum[4];
 93 
 94     bool isLittleEndian = checkEndian();
 95     for (int i = 0; i<4; i++) {
 96         char ch;
 97 
 98         if (isLittleEndian) {
 99             if (i <= len-1)
100                 ch = data[len - 1 - i];
101             else
102                 ch = '\0';
103         }
104         else {
105             if (i <= len-1)
106                 ch = data[i];
107             else
108                 ch = '\0';
109         }
110         localNum[i] = ch;
111     }
112 
113     int num;
114     memcpy(&num, localNum, 4);
115     return num;
116 }
117 
118 /**
119  * 解析IHDR数据
120  */
121 void PNG::parseIHDRData(DataChunkHeader& dataChunkHeader, FILE* pFile)
122 {
123     int dataLen = this->parseNumber(dataChunkHeader.length, 4);
124 
125     IDHRData idhrData;
126     char crc[4];
127 
128     fread(&idhrData, 1, sizeof(IDHRData), pFile);
129     fread(crc, 1, 4, pFile);
130 
131     this->m_width              = this->parseNumber(idhrData.width, 4);
132     this->m_height              = this->parseNumber(idhrData.height, 4);
133     this->m_bitDepth          = this->parseNumber(idhrData.bitDepth, 1);
134     this->m_colorType          = this->parseNumber(idhrData.colorType, 1);
135     this->m_compressionMethod = this->parseNumber(idhrData.compressionMethod, 1);
136     this->m_filterMethod      = this->parseNumber(idhrData.filterMethod, 1);
137     this->m_interlaceMethod   = this->parseNumber(idhrData.interlaceMethod, 1);
138     this->m_chanels              = 0;
139 
140     switch (this->m_colorType) {
141         case PNG_COLOR_TYPE_GRAY:
142         case PNG_COLOR_TYPE_PALETTE:
143             this->m_chanels = 1;
144         break;
145         case PNG_COLOR_TYPE_RGB:
146             this->m_chanels = 3;
147         break;
148         case PNG_COLOR_TYPE_GRAY_ALPHA:
149             this->m_chanels = 2;
150         break;
151         case PNG_COLOR_TYPE_RGB_ALPHA:
152             this->m_chanels = 4;
153         break;
154         default:
155             this->m_chanels = 0;
156         break;
157    }
158 }
159 
160 /**
161  * 解压数据
162  */
163 int PNG::decompressData(z_stream* zStream, unsigned char* data, int dataLen, int leftLen, FILE *pFile)
164 {
165     int result = 0;
166 
167     int leftBytesCount = leftLen;
168     int avail_out = -1;
169     do {
170         if (zStream->avail_in == 0) {
171             if (avail_out == 0) 
172                 break;
173             else {
174                 if (leftBytesCount == 0) {
175                     DataChunkHeader dataChunkHeader;
176                     fread(&dataChunkHeader, 1, sizeof(DataChunkHeader), pFile);
177 
178                     int newDataLen = this->parseNumber(dataChunkHeader.length, 4);
179                     unsigned char* newData = new unsigned char[dataLen + newDataLen];
180                     char crc[4];
181 
182                     fread(newData + dataLen, 1, newDataLen, pFile);
183                     fread(crc, 1, 4, pFile);
184                     memcpy(newData, data, dataLen);
185 
186                     delete data;
187                     data = newData;
188 
189                     zStream->next_in = newData + dataLen;
190                     zStream->avail_in = newDataLen;
191 
192                     dataLen = dataLen + newDataLen;
193                     
194                     return this->decompressData(zStream, data, dataLen, 0, pFile);
195                 }
196             }
197 
198             // 导出数据是否超过限制
199             if (leftBytesCount > DECOMPRESSION_MAX_BYTES)
200                 zStream->avail_in = DECOMPRESSION_MAX_BYTES;
201             else
202                 zStream->avail_in = leftBytesCount;
203 
204             leftBytesCount -= zStream->avail_in;
205         }
206         
207         if (avail_out > 0)
208             zStream->avail_out = avail_out;
209         else
210             zStream->avail_out = m_width * 4 + 1;
211 
212         result = inflate(zStream, Z_NO_FLUSH);
213         if (result != Z_OK)
214             break;
215 
216         avail_out = zStream->avail_out;
217     } while (zStream->avail_in >= 0);
218 
219     return result;
220 }
221 
222 /**
223  * 生成图片数据
224  */
225 void PNG::generateImageData(unsigned char* data, unsigned long dataLen)
226 {
227     // 行字节数
228     int rowBytes = this->m_chanels * this->m_width;
229 
230     // 初始化图片数据
231     this->m_imageData = new unsigned char[rowBytes * this->m_height];
232 
233     unsigned char* pImageData = this->m_imageData;
234     unsigned char* pRowData = data;
235 
236     for (int rowIndex = 0; rowIndex < this->m_height; rowIndex++) {
237         // 过滤类型
238         unsigned char filterType = pRowData[0]; 
239 
240         pRowData += 1;
241 
242         switch (filterType) {
243             // 不需要过滤处理
244             case DATA_FILTER_TYPE_DEFAULT:
245                 this->defaultFilterType(pImageData, pRowData, rowBytes);
246             break;
247             // 当前行相加
248             case DATA_FILTER_TYPE_ADD_ROW:
249                 this->addCurrentRowFilterType(pImageData, pRowData, rowBytes);
250             break;
251             // 和前一行相加
252             case DATA_FILTER_TYPE_ADD_UP:
253                 this->addUpRowFilterType(pImageData, pRowData, rowBytes);
254             break;
255             // 求平均
256             case DATA_FILTER_TYPE_AVERGE:
257                 this->avergeFilterType(pImageData, pRowData, rowBytes);
258             break;
259             // Paeth
260             case DATA_FILTER_TYPE_PAETH:
261                 this->paethFilterType(pImageData, pRowData, rowBytes);
262             break;
263             // 类型错误
264             default:
265             break;
266         }
267 
268         pImageData += rowBytes;
269         pRowData += rowBytes;
270 
271         char text[100];
272         sprintf(text, "filter type:%d, rowIndex:%d \n", filterType, rowIndex);
273         OutputDebugString(text);
274     }
275 
276     int channel = rowBytes / this->m_width;
277     if (channel == 4) {
278         unsigned int *tmp = (unsigned int *)this->m_imageData;
279 
280         for (unsigned short i = 0; i < this->m_height; i++) {
281             for (unsigned int j = 0; j < rowBytes; j+=4) {
282                 unsigned int offset = i * rowBytes + j;
283 
284                 *tmp++ = RGB_USE_ALPHA(
285                     this->m_imageData[offset], 
286                     this->m_imageData[offset+1], 
287                     this->m_imageData[offset+2],
288                     this->m_imageData[offset+3] 
289                 );
290             }
291         }
292     }
293 }
294 
295 /**
296  * 默认的过滤方式
297  */
298 void PNG::defaultFilterType(unsigned char* pImageData, unsigned char* pRowData, int rowBytes)
299 {
300     for (int i = 0; i < rowBytes; i++) {
301         *pImageData++ = *pRowData++;
302     }
303 }
304 
305 /**
306  * 当前行相加的过滤方式
307  */
308 void PNG::addCurrentRowFilterType(unsigned char* pImageData, unsigned char* pRowData, int rowBytes)
309 {
310     for (int i = 0; i < rowBytes; i++) {
311         if (i == 0) {
312             memcpy(pImageData, pRowData, 4);
313             i += 3;
314             pImageData += 4;
315             pRowData += 4;
316         }
317         else {
318             *pImageData++ = (unsigned char)(((int)*(pRowData++) + (int)*(pImageData-4)) & 0xFF);
319         }
320     }
321 }
322 
323 /**
324  * 前一行相加的过滤方式
325  */
326 void PNG::addUpRowFilterType(unsigned char* pImageData, unsigned char* pRowData, int rowBytes)
327 {
328     for (int i = 0; i < rowBytes; i++) {
329         *pImageData++ = (unsigned char)(((int)*(pRowData++) + (int)*(pImageData-rowBytes)) & 0xFF);
330     }
331 }
332 
333 /**
334  * 平均的过滤方式
335  */
336 void PNG::avergeFilterType(unsigned char* pImageData, unsigned char* pRowData, int rowBytes)
337 {
338     for (int i = 0; i < rowBytes; i++) {
339         int averge = 0;
340 
341         if (i <= 3) {
342             averge = ((int)*(pImageData-rowBytes)) / 2;
343 
344             *pImageData++ = (unsigned char)((averge + (int)*(pRowData++)) & 0xFF);
345         }
346         else {
347             averge = (((int)*(pImageData-4)) + ((int)*(pImageData-rowBytes))) / 2;
348 
349             *pImageData++ = (unsigned char)((averge + (int)*(pRowData++)) & 0xFF);
350         }
351     }
352 }
353 
354 /**
355  * paeth的过滤方式
356  */
357 int Paeth(int a, int b, int c)
358 {
359     int p  = a + b - c;
360     int pa = abs(p - a);
361     int pb = abs(p - b);
362     int pc = abs(p - c);
363     
364     int Paeth;
365     if(pa <= pb && pa <= pc)
366         Paeth = a;
367     else if (pb <= pc) 
368         Paeth = b;
369     else 
370         Paeth = c;
371     return Paeth ;
372 }
373 void PNG::paethFilterType(unsigned char* pImageData, unsigned char* pRowData, int rowBytes)
374 {
375     for (int i = 0; i < rowBytes; i++) {
376         if (i <= 3) {
377             *pImageData++ = (unsigned char)(((int)*(pRowData++) + (int)*(pImageData-rowBytes)) & 0xFF);
378         }
379         else {
380             unsigned char left = *(pImageData - 4);
381             unsigned char up = *(pImageData - rowBytes);
382             unsigned char leftUp = *(pImageData - rowBytes - 4);
383 
384             int value = Paeth((int)left, (int)up, (int)leftUp);
385             
386             *pImageData++ = (unsigned char)(((int)*(pRowData++) + value) & 0xFF);
387         }
388     }
389 }
390 
391 /**
392  * 解析IDAT数据
393  */
394 void PNG::parseIDATData(DataChunkHeader& dataChunkHeader, FILE* pFile)
395 {
396     // 解压后的图片数据
397     unsigned char* imageData = new unsigned char[m_width * m_height * 4];
398 
399     int dataLen = this->parseNumber(dataChunkHeader.length, 4);
400     // 解压前的图片数据
401     unsigned char* data = new unsigned char[dataLen];
402     char crc[4];
403     // 提取数据
404     fread(data, 1, dataLen, pFile);
405     fread(crc, 1, 4, pFile);
406 
407     // 存放临时的解压数据
408     unsigned long decompressDataLen = m_width * m_height * 4 + m_height;
409     unsigned char* decompressData = new unsigned char[decompressDataLen];
410 
411     z_stream* zStream = new z_stream();
412     zStream->next_in = data;
413     zStream->next_out = decompressData;
414 
415     inflateInit(zStream);
416 
417     // 解压数据
418     this->decompressData(zStream, data, dataLen, dataLen, pFile);
419     // 生成图片数据
420     this->generateImageData(decompressData, decompressDataLen);
421 
422     /*
423     int result = 0;
424     // 开始解压数据
425     int leftBytesCount = dataLen;
426     int avail_out = -1;
427     do {
428         if (zStream->avail_in == 0) {
429             if (avail_out == 0)
430                 break;
431             else {
432                 if (leftBytesCount == 0) {
433 
434                 }
435             }
436 
437             // 导出数据是否超过限制
438             if (leftBytesCount > DECOMPRESSION_MAX_BYTES)
439                 zStream->avail_in = DECOMPRESSION_MAX_BYTES;
440             else
441                 zStream->avail_in = leftBytesCount;
442 
443             leftBytesCount = dataLen - zStream->avail_in;
444         }
445         
446         if (avail_out > 0)
447             zStream->avail_out = avail_out;
448         else
449             zStream->avail_out = m_width * 4 + 1;
450 
451         result = inflate(zStream, Z_NO_FLUSH);
452         if (result != Z_OK)
453             break;
454 
455         avail_out = zStream->avail_out;
456     } while (zStream->avail_in >= 0);
457     // 数据解压是否成功
458     if (result == Z_STREAM_END) {
459         int i = 1;
460     }
461     */
462 }
463 
464 /**
465  * 解析IEND数据
466  */
467 void PNG::parseIENDData(DataChunkHeader& dataChunkHeader, FILE *pFile)
468 {
469     char crc[4];
470     fread(crc, 1, 4, pFile);
471 }
472 
473 /**
474  * 解析其他数据
475  */
476 void PNG::parseCommonData(DataChunkHeader& dataChunkHeader, FILE *pFile)
477 {
478     int dataLen = this->parseNumber(dataChunkHeader.length, 4);
479     fseek(pFile, dataLen + 4, SEEK_CUR);
480 }
481 
482 /**
483  * 获取图片宽度
484  */
485 unsigned char* PNG::getImageData()
486 {
487     return this->m_imageData;
488 }
489 
490 /**
491  * 获取图片宽度
492  */
493 int PNG::getWindth()
494 {
495     return this->m_width;
496 }
497 
498 /**
499  * 获取图片高度
500  */
501 int PNG::getHeight()
502 {
503     return this->m_height;
504 }

如果需要绘制图片,可以使用opengl库

参考代码:

 1 glViewport(0, 0, winWidth, winHeight);    
 2 
 3 glMatrixMode(GL_PROJECTION);            
 4 glLoadIdentity();                        
 5 glOrtho(0.0f, winWidth - 1.0, 0.0, winHeight - 1.0, -10.0, 10.0);
 6 
 7 glMatrixMode(GL_MODELVIEW);    
 8 glLoadIdentity();        
 9 
10 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
11 
12 glEnable(GL_TEXTURE_2D);
13 
14 int width = png->getWindth();
15 int height = png->getHeight();
16 unsigned char* data = png->getImageData();
17 
18 GLuint name1;
19 glGenTextures(1, &name1);
20 glBindTexture(GL_TEXTURE_2D, name1);
21 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
22 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
23 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,      GL_REPEAT);
24 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,      GL_REPEAT);
25 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,GL_RGBA, GL_UNSIGNED_BYTE, data); 
26 glBegin(GL_POLYGON);
27     glTexCoord2f(1, 1);
28     glVertex3d(800, 800, 2);
29     glTexCoord2f(0, 1);
30     glVertex3d(0, 800, 2);
31     glTexCoord2f(0, 0);
32     glVertex3d(0, 0, 2);
33     glTexCoord2f(1, 0);
34     glVertex3d(800, 0, 2);
35 glEnd();

 

转载于:https://www.cnblogs.com/iRidescent-ZONE/p/4193248.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值