]> git.mxchange.org Git - simgear.git/blob - simgear/screen/texture.cxx
Fix a crash situation.
[simgear.git] / simgear / screen / texture.cxx
1 /*
2  * Texture manipulation routines
3  *
4  * Copyright (c) Mark J. Kilgard, 1997.
5  * Code added in april 2003 by Erik Hofman
6  *
7  * This program is freely distributable without licensing fees 
8  * and is provided without guarantee or warrantee expressed or 
9  * implied. This program is -not- in the public domain.
10  *
11  * $Id$
12  */
13
14 #include <simgear/compiler.h>
15
16 #ifdef WIN32
17 # include <windows.h>
18 #endif
19
20 #include SG_GLU_H
21
22 #include <zlib.h>
23
24 #include "texture.hxx"
25 #include "colours.h"
26
27
28 const char *FILE_OPEN_ERROR = "Unable to open file.";
29 const char *WRONG_COUNT = "Unsupported number of color channels.";
30 const char *NO_TEXTURE = "No texture data resident.";
31 const char *OUT_OF_MEMORY = "Out of memory.";
32
33
34 SGTexture::SGTexture()
35    : texture_id(0),
36      texture_data(0),
37      num_colors(3)
38 {
39 }
40
41 SGTexture::SGTexture(unsigned int width, unsigned int height)
42    : texture_id(0),
43      errstr("")
44 {
45     texture_data = new GLubyte[ width * height * 3 ];
46 }
47
48 SGTexture::~SGTexture()
49 {
50     if ( texture_data ) {
51         delete texture_data;
52     }
53
54     if ( texture_id != 0 ) {
55         free_id();
56     }
57 }
58
59 void
60 SGTexture::bind()
61 {
62     bool gen = false;
63     if (!texture_id) {
64 #ifdef GL_VERSION_1_1
65         glGenTextures(1, &texture_id);
66
67 #elif GL_EXT_texture_object
68         glGenTexturesEXT(1, &texture_id);
69 #endif
70         gen = true;
71     }
72
73 #ifdef GL_VERSION_1_1
74     glBindTexture(GL_TEXTURE_2D, texture_id);
75
76 #elif GL_EXT_texture_object
77     glBindTextureEXT(GL_TEXTURE_2D, texture_id);
78 #endif
79
80     if (gen) {
81         glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
82         glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
83         glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
84         glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
85     }
86 }
87
88 /**
89  * A function to resize the OpenGL window which will be used by
90  * the dynamic texture generating routines.
91  *
92  * @param width The width of the new window
93  * @param height The height of the new window
94  */
95 void
96 SGTexture::resize(unsigned int width, unsigned int height)
97 {
98     GLfloat aspect;
99
100     // Make sure that we don't get a divide by zero exception
101     if (height == 0)
102         height = 1;
103
104     // Set the viewport for the OpenGL window
105     glViewport(0, 0, width, height);
106
107     // Calculate the aspect ratio of the window
108     aspect = width/height;
109
110     // Go to the projection matrix, this gets modified by the perspective
111     // calulations
112     glMatrixMode(GL_PROJECTION);
113     glLoadIdentity();
114
115     // Do the perspective calculations
116     gluPerspective(45.0, aspect, 1.0, 400.0);
117
118     // Return to the modelview matrix
119     glMatrixMode(GL_MODELVIEW);
120 }
121
122 /**
123  * A function to prepare the OpenGL state machine for dynamic
124  * texture generation.
125  *
126  * @param width The width of the texture
127  * @param height The height of the texture
128  */
129 void
130 SGTexture::prepare(unsigned int width, unsigned int height) {
131
132     texture_width = width;
133     texture_height = height;
134
135     // Resize the OpenGL window to the size of our dynamic texture
136     resize(texture_width, texture_height);
137
138     glClearColor(0.0, 0.0, 0.0, 1.0);
139 }
140
141 /**
142  * A function to generate the dynamic texture.
143  *
144  * The actual texture can be accessed by calling get_texture()
145  *
146  * @param width The width of the previous OpenGL window
147  * @param height The height of the previous OpenGL window
148  */
149 void
150 SGTexture::finish(unsigned int width, unsigned int height) {
151     // If a texture hasn't been created then it gets created, and the contents
152     // of the frame buffer gets copied into it. If the texture has already been
153     // created then its contents just get updated.
154     bind();
155     if (!texture_data)
156     {
157       // Copies the contents of the frame buffer into the texture
158       glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0,
159                                       texture_width, texture_height, 0);
160
161     } else {
162       // Copies the contents of the frame buffer into the texture
163       glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0,
164                                          texture_width, texture_height);
165     }
166
167     // Set the OpenGL window back to its previous size
168     resize(width, height);
169
170     // Clear the window back to black
171     glClearColor(0.0, 0.0, 0.0, 1.0);
172     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
173 }
174
175
176 void
177 SGTexture::read_alpha_texture(const char *name)
178 {
179     GLubyte *lptr;
180     SGTexture::ImageRec *image;
181     int y;
182
183     if (texture_data)
184         delete texture_data;
185
186     image = ImageOpen(name);
187     if(!image) {
188         errstr = FILE_OPEN_ERROR;
189         return;
190     }
191
192     texture_width = image->xsize;
193     texture_height = image->ysize;
194
195     // printf("image->zsize = %d\n", image->zsize);
196
197     if (image->zsize != 1) {
198       ImageClose(image);
199       errstr = WRONG_COUNT;
200       return;
201     }
202
203     texture_data = new GLubyte[ image->xsize * image->ysize ];
204     num_colors = 1;
205     if (!texture_data) {
206         errstr = NO_TEXTURE;
207         return;
208     }
209
210     lptr = texture_data;
211     for(y=0; y<image->ysize; y++) {
212         ImageGetRow(image,lptr,y,0);
213         lptr += image->xsize;
214     }
215     ImageClose(image);
216 }
217
218 void
219 SGTexture::read_rgb_texture(const char *name)
220 {
221     GLubyte *ptr;
222     GLubyte *rbuf, *gbuf, *bbuf, *abuf;
223     SGTexture::ImageRec *image;
224     int y;
225
226     if (texture_data)
227         delete texture_data;
228
229     image = ImageOpen(name);
230     if(!image) {
231         errstr = FILE_OPEN_ERROR;
232         return;
233     }
234
235     texture_width = image->xsize;
236     texture_height = image->ysize;
237     if (image->zsize != 3 && image->zsize != 4) {
238       ImageClose(image);
239       errstr = WRONG_COUNT;
240       return;
241     }
242
243     texture_data = new GLubyte[ image->xsize * image->ysize * 3 ];
244     num_colors = 3;
245     rbuf = new GLubyte[ image->xsize ];
246     gbuf = new GLubyte[ image->xsize ];
247     bbuf = new GLubyte[ image->xsize ];
248     abuf = new GLubyte[ image->xsize ];
249     if(!texture_data || !rbuf || !gbuf || !bbuf || !abuf) {
250       delete texture_data;
251       delete rbuf;
252       delete gbuf;
253       delete bbuf;
254       delete abuf;
255       errstr = OUT_OF_MEMORY;
256       return;
257     }
258
259     ptr = texture_data;
260     for(y=0; y<image->ysize; y++) {
261         if(image->zsize == 4) {
262             ImageGetRow(image,rbuf,y,0);
263             ImageGetRow(image,gbuf,y,1);
264             ImageGetRow(image,bbuf,y,2);
265             ImageGetRow(image,abuf,y,3); // discard
266             rgbtorgb(rbuf,gbuf,bbuf,ptr,image->xsize);
267             ptr += (image->xsize * 3);
268         } else {
269             ImageGetRow(image,rbuf,y,0);
270             ImageGetRow(image,gbuf,y,1);
271             ImageGetRow(image,bbuf,y,2);
272             rgbtorgb(rbuf,gbuf,bbuf,ptr,image->xsize);
273             ptr += (image->xsize * 3);
274         }
275     }
276
277     ImageClose(image);
278     delete rbuf;
279     delete gbuf;
280     delete bbuf;
281     delete abuf;
282 }
283
284
285
286 void
287 SGTexture::read_rgba_texture(const char *name)
288 {
289     GLubyte *ptr;
290     GLubyte *rbuf, *gbuf, *bbuf, *abuf;
291     SGTexture::ImageRec *image;
292     int y;
293
294     if (texture_data)
295         delete texture_data;
296
297     image = ImageOpen(name);
298     if(!image) {
299         errstr = FILE_OPEN_ERROR;
300         return;
301     }
302
303     texture_width = image->xsize;
304     texture_height = image->ysize;
305     if (image->zsize != 3 && image->zsize != 4) {
306       ImageClose(image);
307       errstr = WRONG_COUNT;
308       return;
309     }
310
311     texture_data = new GLubyte[ image->xsize * image->ysize * 4 ];
312     num_colors = 4;
313     rbuf = new GLubyte[ image->xsize ];
314     gbuf = new GLubyte[ image->xsize ];
315     bbuf = new GLubyte[ image->xsize ];
316     abuf = new GLubyte[ image->xsize ];
317     if(!texture_data || !rbuf || !gbuf || !bbuf || !abuf) {
318       delete texture_data;
319       delete rbuf;
320       delete gbuf;
321       delete bbuf;
322       delete abuf;
323       errstr = OUT_OF_MEMORY;
324       return;
325     }
326
327     ptr = texture_data;
328     memset(abuf, 255, image->xsize);
329     for(y=0; y<image->ysize; y++) {
330         if(image->zsize == 4) {
331             ImageGetRow(image,rbuf,y,0);
332             ImageGetRow(image,gbuf,y,1);
333             ImageGetRow(image,bbuf,y,2);
334             ImageGetRow(image,abuf,y,3);
335             rgbatorgba(rbuf,gbuf,bbuf,abuf,ptr,image->xsize);
336             ptr += (image->xsize * 4);
337         } else {
338             ImageGetRow(image,rbuf,y,0);
339             ImageGetRow(image,gbuf,y,1);
340             ImageGetRow(image,bbuf,y,2);
341             rgbatorgba(rbuf,gbuf,bbuf,abuf,ptr,image->xsize);
342             ptr += (image->xsize * 3);
343         }
344     }
345
346     ImageClose(image);
347     delete rbuf;
348     delete gbuf;
349     delete bbuf;
350     delete abuf;
351 }
352
353 void
354 SGTexture::read_raw_texture(const char *name)
355 {
356     GLubyte *ptr;
357     SGTexture::ImageRec *image;
358     int y;
359
360     if (texture_data)
361         delete texture_data;
362
363     image = RawImageOpen(name);
364
365     if(!image) {
366         errstr = FILE_OPEN_ERROR;
367         return;
368     }
369
370     texture_width = 256;
371     texture_height = 256;
372
373     texture_data = new GLubyte[ 256 * 256 * 3 ];
374     if(!texture_data) {
375       errstr = OUT_OF_MEMORY;
376       return;
377     }
378
379     ptr = texture_data;
380     for(y=0; y<256; y++) {
381         gzread(image->gzfile, ptr, 256*3);
382         ptr+=256*3;
383     }
384     ImageClose(image);
385 }
386
387 void
388 SGTexture::read_r8_texture(const char *name)
389 {
390     unsigned char c[1];
391     GLubyte *ptr;
392     SGTexture::ImageRec *image;
393     int xy;
394
395     if (texture_data)
396         delete texture_data;
397
398     //it wouldn't make sense to write a new function ...
399     image = RawImageOpen(name);
400
401     if(!image) {
402         errstr = FILE_OPEN_ERROR;
403         return;
404     }
405
406     texture_width = 256;
407     texture_height = 256;
408
409     texture_data = new GLubyte [ 256 * 256 * 3 ];
410     if(!texture_data) {
411         errstr = OUT_OF_MEMORY;
412         return;
413     }
414
415     ptr = texture_data;
416     for(xy=0; xy<(256*256); xy++) {
417         gzread(image->gzfile, c, 1);
418
419         //look in the table for the right colours
420         ptr[0]=msfs_colour[c[0]][0];
421         ptr[1]=msfs_colour[c[0]][1];
422         ptr[2]=msfs_colour[c[0]][2];
423
424         ptr+=3;
425     }
426     ImageClose(image);
427 }
428
429
430 void
431 SGTexture::write_texture(const char *name) {
432    SGTexture::ImageRec *image;
433    int x, y;
434
435    image=ImageWriteOpen(name);
436
437    GLubyte *ptr = texture_data;
438    for (y=0; y<texture_height; y++) {
439        for (x=0; x<texture_width; x++) {
440            image->tmp[x]=*ptr;
441            ptr = ptr + num_colors;
442        }
443        fwrite(image->tmp, 1, texture_width, image->file);
444    }
445
446    if (num_colors > 1) {
447       ptr = texture_data + 1;
448       for (y=0; y<texture_height; y++) {
449           for (x=0; x<texture_width; x++) {
450              image->tmp[x]=*ptr;
451              ptr = ptr + num_colors;
452           }
453           fwrite(image->tmp, 1, texture_width, image->file);
454       }
455
456       if (num_colors > 2) {
457          ptr = texture_data + 2;
458          for (y=0; y<texture_height; y++) {
459              for (x=0; x<texture_width; x++) {
460                  image->tmp[x]=*ptr;
461                   ptr = ptr + num_colors;
462              }
463              fwrite(image->tmp, 1, texture_width, image->file);
464          }
465
466          if (num_colors > 3) {
467             ptr = texture_data + 3;
468             for (y=0; y<texture_height; y++) {
469                 for (x=0; x<texture_width; x++) {
470                     image->tmp[x]=*ptr;
471                     ptr = ptr + num_colors;
472                 }
473                 fwrite(image->tmp, 1, texture_width, image->file);
474             }
475          }
476       }
477    }
478
479    ImageClose(image);
480 }
481
482
483 void
484 SGTexture::set_pixel(GLuint x, GLuint y, sgVec3 &c)
485 {
486     if (!texture_data) {
487         errstr = NO_TEXTURE;
488         return;
489     }
490
491     unsigned int pos = (x + y*texture_width)*3;
492     texture_data[pos]   = (unsigned char)(c[0] * 256);
493     texture_data[pos+1] = (unsigned char)(c[1] * 256);
494     texture_data[pos+2] = (unsigned char)(c[2] * 256);
495 }
496
497
498 float *
499 SGTexture::get_pixel(GLuint x, GLuint y)
500 {
501     static sgVec3 c;
502
503     sgSetVec3(c, 0, 0, 0);
504     if (!texture_data) {
505         errstr = NO_TEXTURE;
506         return c;
507     }
508
509     unsigned int pos = (x + y*texture_width)*3;
510
511     sgSetVec3(c, texture_data[pos], texture_data[pos+1], texture_data[pos+2]);
512
513     return c;
514 }
515
516 SGTexture::ImageRec *
517 SGTexture::ImageOpen(const char *fileName)
518 {
519      union {
520        int testWord;
521        char testByte[4];
522      } endianTest;
523
524     SGTexture::ImageRec *image;
525     int swapFlag;
526     int x;
527
528     endianTest.testWord = 1;
529     if (endianTest.testByte[0] == 1) {
530         swapFlag = 1;
531     } else {
532         swapFlag = 0;
533     }
534
535     image = new SGTexture::ImageRec;
536     memset(image, 0, sizeof(SGTexture::ImageRec));
537     if (image == 0) {
538         errstr = OUT_OF_MEMORY;
539         return 0;
540     }
541     if ((image->gzfile = gzopen(fileName, "rb")) == 0) {
542       errstr = FILE_OPEN_ERROR;
543       return 0;
544     }
545
546     gzread(image->gzfile, image, 12);
547
548     if (swapFlag) {
549         ConvertShort(&image->imagic, 6);
550     }
551
552     image->tmp = new GLubyte[ image->xsize * 256 ];
553     if (image->tmp == 0) {
554         errstr = OUT_OF_MEMORY;
555         return 0;
556     }
557
558     if ((image->type & 0xFF00) == 0x0100) {
559         x = image->ysize * image->zsize * (int) sizeof(unsigned);
560         image->rowStart = new unsigned[x];
561         image->rowSize = new int[x];
562         if (image->rowStart == 0 || image->rowSize == 0) {
563             errstr = OUT_OF_MEMORY;
564             return 0;
565         }
566         image->rleEnd = 512 + (2 * x);
567         gzseek(image->gzfile, 512, SEEK_SET);
568         gzread(image->gzfile, image->rowStart, x);
569         gzread(image->gzfile, image->rowSize, x);
570         if (swapFlag) {
571             ConvertUint(image->rowStart, x/(int) sizeof(unsigned));
572             ConvertUint((unsigned *)image->rowSize, x/(int) sizeof(int));
573         }
574     }
575     return image;
576 }
577
578
579 void
580 SGTexture::ImageClose(SGTexture::ImageRec *image) {
581     if (image->gzfile)  gzclose(image->gzfile);
582     if (image->file)    fclose(image->file);
583     delete image->tmp;
584     delete image;
585 }
586
587 SGTexture::ImageRec *
588 SGTexture::RawImageOpen(const char *fileName)
589 {
590      union {
591        int testWord;
592        char testByte[4];
593      } endianTest;
594
595     SGTexture::ImageRec *image;
596     int swapFlag;
597
598     endianTest.testWord = 1;
599     if (endianTest.testByte[0] == 1) {
600         swapFlag = 1;
601     } else {
602         swapFlag = 0;
603     }
604
605     image = new SGTexture::ImageRec;
606     memset(image, 0, sizeof(SGTexture::ImageRec));
607     if (image == 0) {
608         errstr = OUT_OF_MEMORY;
609         return 0;
610     }
611     if ((image->gzfile = gzopen(fileName, "rb")) == 0) {
612       errstr = FILE_OPEN_ERROR;
613       return 0;
614     }
615
616     gzread(image->gzfile, image, 12);
617
618     if (swapFlag) {
619         ConvertShort(&image->imagic, 6);
620     }
621
622
623     //just allocate a pseudo value as I'm too lazy to change ImageClose()...
624     image->tmp = new GLubyte;
625
626     if (image->tmp == 0) {
627         errstr = OUT_OF_MEMORY;
628         return 0;
629     }
630
631     return image;
632 }
633
634 SGTexture::ImageRec *
635 SGTexture::ImageWriteOpen(const char *fileName)
636 {
637     union {
638         int testWord;
639         char testByte[4];
640     } endianTest;
641     ImageRec* image;
642     int swapFlag;
643     int x;
644
645     endianTest.testWord = 1;
646     if (endianTest.testByte[0] == 1) {
647         swapFlag = 1;
648     } else {
649         swapFlag = 0;
650     }
651
652     image = new SGTexture::ImageRec;
653     memset(image, 0, sizeof(SGTexture::ImageRec));
654     if (image == 0) {
655         errstr = OUT_OF_MEMORY;
656         return 0;
657     }
658     if ((image->file = fopen(fileName, "w")) == 0) {
659         errstr = FILE_OPEN_ERROR;
660         return 0;
661     }
662
663     image->imagic = 474;
664     image->type = 0x0001;
665     image->dim = 0;
666     image->xsize = texture_width;
667     image->ysize = texture_height;
668     image->zsize = num_colors;
669
670     fwrite(image, 1, 12, image->file);
671
672     fseek(image->file, 512, SEEK_SET);
673     if (swapFlag) {
674         ConvertShort(&image->imagic, 6);
675     }
676
677     image->tmp = new GLubyte[ image->xsize * 256 ];
678     if (image->tmp == 0) {
679         errstr = OUT_OF_MEMORY;
680         return 0;
681     }
682
683     if ((image->type & 0xFF00) == 0x0100) {
684         x = image->ysize * image->zsize * (int) sizeof(unsigned);
685         image->rowStart = new unsigned[x];
686         image->rowSize = new int[x];
687         if (image->rowStart == 0 || image->rowSize == 0) {
688             errstr = OUT_OF_MEMORY;
689             return 0;
690         }
691         image->rleEnd = 512 + (2 * x);
692         fseek(image->file, 512, SEEK_SET);
693         fread(image->rowStart, 1, x, image->file);
694         fread(image->rowSize, 1, x, image->file);
695         if (swapFlag) {
696             ConvertUint(image->rowStart, x/(int) sizeof(unsigned));
697             ConvertUint((unsigned *)image->rowSize, x/(int) sizeof(int));
698         }
699     }
700     return image;
701
702 }
703
704 void
705 SGTexture::ImageGetRow(SGTexture::ImageRec *image, GLubyte *buf, int y, int z) {
706     GLubyte *iPtr, *oPtr, pixel;
707     int count;
708
709     if ((image->type & 0xFF00) == 0x0100) {
710         gzseek(image->gzfile, (long) image->rowStart[y+z*image->ysize], SEEK_SET);
711         gzread(image->gzfile, image->tmp,
712                (unsigned int)image->rowSize[y+z*image->ysize]);
713
714         iPtr = image->tmp;
715         oPtr = buf;
716         for (;;) {
717             pixel = *iPtr++;
718             count = (int)(pixel & 0x7F);
719             if (!count) {
720                 errstr = WRONG_COUNT;
721                 return;
722             }
723             if (pixel & 0x80) {
724                 while (count--) {
725                     *oPtr++ = *iPtr++;
726                 }
727             } else {
728                 pixel = *iPtr++;
729                 while (count--) {
730                     *oPtr++ = pixel;
731                 }
732             }
733         }
734     } else {
735         gzseek(image->gzfile, 512+(y*image->xsize)+(z*image->xsize*image->ysize),
736               SEEK_SET);
737         gzread(image->gzfile, buf, image->xsize);
738     }
739 }
740
741 void
742 SGTexture::ImagePutRow(SGTexture::ImageRec *image, GLubyte *buf, int y, int z) {
743     GLubyte *iPtr, *oPtr, pixel;
744     int count;
745
746     if ((image->type & 0xFF00) == 0x0100) {
747         fseek(image->file, (long) image->rowStart[y+z*image->ysize], SEEK_SET);
748         fread( image->tmp, 1, (unsigned int)image->rowSize[y+z*image->ysize],
749                image->file);
750
751         iPtr = image->tmp;
752         oPtr = buf;
753         for (;;) {
754             pixel = *iPtr++;
755             count = (int)(pixel & 0x7F);
756             if (!count) {
757                 errstr = WRONG_COUNT;
758                 return;
759             }
760             if (pixel & 0x80) {
761                 while (count--) {
762                     *oPtr++ = *iPtr++;
763                 }
764             } else {
765                 pixel = *iPtr++;
766                 while (count--) {
767                     *oPtr++ = pixel;
768                 }
769             }
770         }
771     } else {
772         fseek(image->file, 512+(y*image->xsize)+(z*image->xsize*image->ysize),
773               SEEK_SET);
774         fread(buf, 1, image->xsize, image->file);
775     }
776 }
777
778
779 void
780 SGTexture::rgbtorgb(GLubyte *r, GLubyte *g, GLubyte *b, GLubyte *l, int n) {
781     while(n--) {
782         l[0] = r[0];
783         l[1] = g[0];
784         l[2] = b[0];
785         l += 3; r++; g++; b++;
786     }
787 }
788
789 void
790 SGTexture::rgbatorgba(GLubyte *r, GLubyte *g, GLubyte *b, GLubyte *a,
791                       GLubyte *l, int n) {
792     while(n--) {
793         l[0] = r[0];
794         l[1] = g[0];
795         l[2] = b[0];
796         l[3] = a[0];
797         l += 4; r++; g++; b++; a++;
798     }
799 }
800
801
802 void
803 SGTexture::ConvertShort(unsigned short *array, unsigned int length) {
804     unsigned short b1, b2;
805     unsigned char *ptr;
806
807     ptr = (unsigned char *)array;
808     while (length--) {
809         b1 = *ptr++;
810         b2 = *ptr++;
811         *array++ = (b1 << 8) | (b2);
812     }
813 }
814
815
816 void
817 SGTexture::ConvertUint(unsigned *array, unsigned int length) {
818     unsigned int b1, b2, b3, b4;
819     unsigned char *ptr;
820
821     ptr = (unsigned char *)array;
822     while (length--) {
823         b1 = *ptr++;
824         b2 = *ptr++;
825         b3 = *ptr++;
826         b4 = *ptr++;
827         *array++ = (b1 << 24) | (b2 << 16) | (b3 << 8) | (b4);
828     }
829 }
830