1ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu/* 2ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu * Copyright (C) Texas Instruments - http://www.ti.com/ 3ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu * 4ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu * Licensed under the Apache License, Version 2.0 (the "License"); 5ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu * you may not use this file except in compliance with the License. 6ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu * You may obtain a copy of the License at 7ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu * 8ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu * http://www.apache.org/licenses/LICENSE-2.0 9ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu * 10ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu * Unless required by applicable law or agreed to in writing, software 11ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu * distributed under the License is distributed on an "AS IS" BASIS, 12ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu * See the License for the specific language governing permissions and 14ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu * limitations under the License. 15ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu */ 16ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu 17ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu/** 18ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu* @file Encoder_libjpeg.h 19ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu* 20ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu* This defines API for camerahal to encode YUV using libjpeg 21ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu* 22ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu*/ 23ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu 24ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu#ifndef ANDROID_CAMERA_HARDWARE_ENCODER_LIBJPEG_H 25ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu#define ANDROID_CAMERA_HARDWARE_ENCODER_LIBJPEG_H 26ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu 27ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu#include <utils/threads.h> 28ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu#include <utils/RefBase.h> 29ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu 3036e9bdd56757ff8048e08f6e52f234480c44f122Tyler Luuextern "C" { 3136e9bdd56757ff8048e08f6e52f234480c44f122Tyler Luu#include "jhead.h" 3236e9bdd56757ff8048e08f6e52f234480c44f122Tyler Luu} 330c7ae887ee41852f275887fd543fae810668e2d1Tyler Luu 340c7ae887ee41852f275887fd543fae810668e2d1Tyler Luu#define CANCEL_TIMEOUT 3000000 // 3 seconds 350c7ae887ee41852f275887fd543fae810668e2d1Tyler Luu 36ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luunamespace android { 37ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu/** 38ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu * libjpeg encoder class - uses libjpeg to encode yuv 39ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu */ 40ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu 4136e9bdd56757ff8048e08f6e52f234480c44f122Tyler Luu#define MAX_EXIF_TAGS_SUPPORTED 30 42c160a1f85c70e49a7613d774e2d99035f3cb4851Tyler Luutypedef void (*encoder_libjpeg_callback_t) (void* main_jpeg, 43c160a1f85c70e49a7613d774e2d99035f3cb4851Tyler Luu void* thumb_jpeg, 44ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu CameraFrame::FrameType type, 45ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu void* cookie1, 46ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu void* cookie2, 470c7ae887ee41852f275887fd543fae810668e2d1Tyler Luu void* cookie3, 480c7ae887ee41852f275887fd543fae810668e2d1Tyler Luu bool canceled); 49ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu 50ef73bec8a83a2fd7fbb072d009382c64eb2da0eeTyler Luu// these have to match strings defined in external/jhead/exif.c 5136e9bdd56757ff8048e08f6e52f234480c44f122Tyler Luustatic const char TAG_MODEL[] = "Model"; 5236e9bdd56757ff8048e08f6e52f234480c44f122Tyler Luustatic const char TAG_MAKE[] = "Make"; 5336e9bdd56757ff8048e08f6e52f234480c44f122Tyler Luustatic const char TAG_FOCALLENGTH[] = "FocalLength"; 5436e9bdd56757ff8048e08f6e52f234480c44f122Tyler Luustatic const char TAG_DATETIME[] = "DateTime"; 5536e9bdd56757ff8048e08f6e52f234480c44f122Tyler Luustatic const char TAG_IMAGE_WIDTH[] = "ImageWidth"; 5636e9bdd56757ff8048e08f6e52f234480c44f122Tyler Luustatic const char TAG_IMAGE_LENGTH[] = "ImageLength"; 5736e9bdd56757ff8048e08f6e52f234480c44f122Tyler Luustatic const char TAG_GPS_LAT[] = "GPSLatitude"; 5836e9bdd56757ff8048e08f6e52f234480c44f122Tyler Luustatic const char TAG_GPS_LAT_REF[] = "GPSLatitudeRef"; 5936e9bdd56757ff8048e08f6e52f234480c44f122Tyler Luustatic const char TAG_GPS_LONG[] = "GPSLongitude"; 6036e9bdd56757ff8048e08f6e52f234480c44f122Tyler Luustatic const char TAG_GPS_LONG_REF[] = "GPSLongitudeRef"; 6136e9bdd56757ff8048e08f6e52f234480c44f122Tyler Luustatic const char TAG_GPS_ALT[] = "GPSAltitude"; 6236e9bdd56757ff8048e08f6e52f234480c44f122Tyler Luustatic const char TAG_GPS_ALT_REF[] = "GPSAltitudeRef"; 6336e9bdd56757ff8048e08f6e52f234480c44f122Tyler Luustatic const char TAG_GPS_MAP_DATUM[] = "GPSMapDatum"; 6436e9bdd56757ff8048e08f6e52f234480c44f122Tyler Luustatic const char TAG_GPS_PROCESSING_METHOD[] = "GPSProcessingMethod"; 6536e9bdd56757ff8048e08f6e52f234480c44f122Tyler Luustatic const char TAG_GPS_VERSION_ID[] = "GPSVersionID"; 6636e9bdd56757ff8048e08f6e52f234480c44f122Tyler Luustatic const char TAG_GPS_TIMESTAMP[] = "GPSTimeStamp"; 6736e9bdd56757ff8048e08f6e52f234480c44f122Tyler Luustatic const char TAG_GPS_DATESTAMP[] = "GPSDateStamp"; 6836e9bdd56757ff8048e08f6e52f234480c44f122Tyler Luustatic const char TAG_ORIENTATION[] = "Orientation"; 69ef73bec8a83a2fd7fbb072d009382c64eb2da0eeTyler Luustatic const char TAG_FLASH[] = "Flash"; 70ef73bec8a83a2fd7fbb072d009382c64eb2da0eeTyler Luustatic const char TAG_DIGITALZOOMRATIO[] = "DigitalZoomRatio"; 71ef73bec8a83a2fd7fbb072d009382c64eb2da0eeTyler Luustatic const char TAG_EXPOSURETIME[] = "ExposureTime"; 72ef73bec8a83a2fd7fbb072d009382c64eb2da0eeTyler Luustatic const char TAG_APERTURE[] = "ApertureValue"; 73ef73bec8a83a2fd7fbb072d009382c64eb2da0eeTyler Luustatic const char TAG_ISO_EQUIVALENT[] = "ISOSpeedRatings"; 74ef73bec8a83a2fd7fbb072d009382c64eb2da0eeTyler Luustatic const char TAG_WHITEBALANCE[] = "WhiteBalance"; 75ef73bec8a83a2fd7fbb072d009382c64eb2da0eeTyler Luustatic const char TAG_LIGHT_SOURCE[] = "LightSource"; 76ef73bec8a83a2fd7fbb072d009382c64eb2da0eeTyler Luustatic const char TAG_METERING_MODE[] = "MeteringMode"; 77ef73bec8a83a2fd7fbb072d009382c64eb2da0eeTyler Luustatic const char TAG_EXPOSURE_PROGRAM[] = "ExposureProgram"; 78ef73bec8a83a2fd7fbb072d009382c64eb2da0eeTyler Luustatic const char TAG_COLOR_SPACE[] = "ColorSpace"; 79ef73bec8a83a2fd7fbb072d009382c64eb2da0eeTyler Luustatic const char TAG_CPRS_BITS_PER_PIXEL[] = "CompressedBitsPerPixel"; 80ef73bec8a83a2fd7fbb072d009382c64eb2da0eeTyler Luustatic const char TAG_FNUMBER[] = "FNumber"; 81ef73bec8a83a2fd7fbb072d009382c64eb2da0eeTyler Luustatic const char TAG_SHUTTERSPEED[] = "ShutterSpeedValue"; 82ef73bec8a83a2fd7fbb072d009382c64eb2da0eeTyler Luustatic const char TAG_SENSING_METHOD[] = "SensingMethod"; 83ef73bec8a83a2fd7fbb072d009382c64eb2da0eeTyler Luustatic const char TAG_CUSTOM_RENDERED[] = "CustomRendered"; 8436e9bdd56757ff8048e08f6e52f234480c44f122Tyler Luu 8536e9bdd56757ff8048e08f6e52f234480c44f122Tyler Luuclass ExifElementsTable { 8636e9bdd56757ff8048e08f6e52f234480c44f122Tyler Luu public: 8736e9bdd56757ff8048e08f6e52f234480c44f122Tyler Luu ExifElementsTable() : 8836e9bdd56757ff8048e08f6e52f234480c44f122Tyler Luu gps_tag_count(0), exif_tag_count(0), position(0), 891e3a7ee3fced4b9723f65147ae856bba7d4e2342Angus Kong jpeg_opened(false), has_datetime_tag(false) { } 9036e9bdd56757ff8048e08f6e52f234480c44f122Tyler Luu ~ExifElementsTable(); 9136e9bdd56757ff8048e08f6e52f234480c44f122Tyler Luu 9236e9bdd56757ff8048e08f6e52f234480c44f122Tyler Luu status_t insertElement(const char* tag, const char* value); 9336e9bdd56757ff8048e08f6e52f234480c44f122Tyler Luu void insertExifToJpeg(unsigned char* jpeg, size_t jpeg_size); 94c160a1f85c70e49a7613d774e2d99035f3cb4851Tyler Luu status_t insertExifThumbnailImage(const char*, int); 9536e9bdd56757ff8048e08f6e52f234480c44f122Tyler Luu void saveJpeg(unsigned char* picture, size_t jpeg_size); 96c78626b15e9f29a5bcf85447ceafb17dcbf58b69Emilian Peev static const char* degreesToExifOrientation(unsigned int); 97c11c07d676f130e6e28ab1611f4862a01a160389Tyler Luu static void stringToRational(const char*, unsigned int*, unsigned int*); 98c11c07d676f130e6e28ab1611f4862a01a160389Tyler Luu static bool isAsciiTag(const char* tag); 9936e9bdd56757ff8048e08f6e52f234480c44f122Tyler Luu private: 10036e9bdd56757ff8048e08f6e52f234480c44f122Tyler Luu ExifElement_t table[MAX_EXIF_TAGS_SUPPORTED]; 10136e9bdd56757ff8048e08f6e52f234480c44f122Tyler Luu unsigned int gps_tag_count; 10236e9bdd56757ff8048e08f6e52f234480c44f122Tyler Luu unsigned int exif_tag_count; 10336e9bdd56757ff8048e08f6e52f234480c44f122Tyler Luu unsigned int position; 10436e9bdd56757ff8048e08f6e52f234480c44f122Tyler Luu bool jpeg_opened; 1051e3a7ee3fced4b9723f65147ae856bba7d4e2342Angus Kong bool has_datetime_tag; 10636e9bdd56757ff8048e08f6e52f234480c44f122Tyler Luu}; 10736e9bdd56757ff8048e08f6e52f234480c44f122Tyler Luu 108ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luuclass Encoder_libjpeg : public Thread { 109c160a1f85c70e49a7613d774e2d99035f3cb4851Tyler Luu /* public member types and variables */ 110c160a1f85c70e49a7613d774e2d99035f3cb4851Tyler Luu public: 111c160a1f85c70e49a7613d774e2d99035f3cb4851Tyler Luu struct params { 112c160a1f85c70e49a7613d774e2d99035f3cb4851Tyler Luu uint8_t* src; 113c160a1f85c70e49a7613d774e2d99035f3cb4851Tyler Luu int src_size; 114c160a1f85c70e49a7613d774e2d99035f3cb4851Tyler Luu uint8_t* dst; 115c160a1f85c70e49a7613d774e2d99035f3cb4851Tyler Luu int dst_size; 116c160a1f85c70e49a7613d774e2d99035f3cb4851Tyler Luu int quality; 117c160a1f85c70e49a7613d774e2d99035f3cb4851Tyler Luu int in_width; 118c160a1f85c70e49a7613d774e2d99035f3cb4851Tyler Luu int in_height; 119c160a1f85c70e49a7613d774e2d99035f3cb4851Tyler Luu int out_width; 120c160a1f85c70e49a7613d774e2d99035f3cb4851Tyler Luu int out_height; 121d9520b9de06f01b9411307040cf245e6fc7fe361Milen Mitkov int right_crop; 122d9520b9de06f01b9411307040cf245e6fc7fe361Milen Mitkov int start_offset; 123c160a1f85c70e49a7613d774e2d99035f3cb4851Tyler Luu const char* format; 124c160a1f85c70e49a7613d774e2d99035f3cb4851Tyler Luu size_t jpeg_size; 125c160a1f85c70e49a7613d774e2d99035f3cb4851Tyler Luu }; 126c160a1f85c70e49a7613d774e2d99035f3cb4851Tyler Luu /* public member functions */ 127ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu public: 128c160a1f85c70e49a7613d774e2d99035f3cb4851Tyler Luu Encoder_libjpeg(params* main_jpeg, 129c160a1f85c70e49a7613d774e2d99035f3cb4851Tyler Luu params* tn_jpeg, 130c160a1f85c70e49a7613d774e2d99035f3cb4851Tyler Luu encoder_libjpeg_callback_t cb, 131c160a1f85c70e49a7613d774e2d99035f3cb4851Tyler Luu CameraFrame::FrameType type, 132c160a1f85c70e49a7613d774e2d99035f3cb4851Tyler Luu void* cookie1, 133c160a1f85c70e49a7613d774e2d99035f3cb4851Tyler Luu void* cookie2, 134c160a1f85c70e49a7613d774e2d99035f3cb4851Tyler Luu void* cookie3) 135c160a1f85c70e49a7613d774e2d99035f3cb4851Tyler Luu : Thread(false), mMainInput(main_jpeg), mThumbnailInput(tn_jpeg), mCb(cb), 1368e52e3bcc31f65a699c25557cf3026d324e631b4Tyler Luu mCancelEncoding(false), mCookie1(cookie1), mCookie2(cookie2), mCookie3(cookie3), 1378e52e3bcc31f65a699c25557cf3026d324e631b4Tyler Luu mType(type), mThumb(NULL) { 138ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu this->incStrong(this); 1390c7ae887ee41852f275887fd543fae810668e2d1Tyler Luu mCancelSem.Create(0); 140ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu } 141ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu 142ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu ~Encoder_libjpeg() { 1438e52e3bcc31f65a699c25557cf3026d324e631b4Tyler Luu CAMHAL_LOGVB("~Encoder_libjpeg(%p)", this); 144ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu } 145ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu 146ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu virtual bool threadLoop() { 147c160a1f85c70e49a7613d774e2d99035f3cb4851Tyler Luu size_t size = 0; 148c160a1f85c70e49a7613d774e2d99035f3cb4851Tyler Luu sp<Encoder_libjpeg> tn = NULL; 149c160a1f85c70e49a7613d774e2d99035f3cb4851Tyler Luu if (mThumbnailInput) { 150c160a1f85c70e49a7613d774e2d99035f3cb4851Tyler Luu // start thread to encode thumbnail 1518e52e3bcc31f65a699c25557cf3026d324e631b4Tyler Luu mThumb = new Encoder_libjpeg(mThumbnailInput, NULL, NULL, mType, NULL, NULL, NULL); 1528e52e3bcc31f65a699c25557cf3026d324e631b4Tyler Luu mThumb->run(); 153c160a1f85c70e49a7613d774e2d99035f3cb4851Tyler Luu } 154c160a1f85c70e49a7613d774e2d99035f3cb4851Tyler Luu 155c160a1f85c70e49a7613d774e2d99035f3cb4851Tyler Luu // encode our main image 156c160a1f85c70e49a7613d774e2d99035f3cb4851Tyler Luu size = encode(mMainInput); 157c160a1f85c70e49a7613d774e2d99035f3cb4851Tyler Luu 1580c7ae887ee41852f275887fd543fae810668e2d1Tyler Luu // signal cancel semaphore incase somebody is waiting 1590c7ae887ee41852f275887fd543fae810668e2d1Tyler Luu mCancelSem.Signal(); 1600c7ae887ee41852f275887fd543fae810668e2d1Tyler Luu 161c160a1f85c70e49a7613d774e2d99035f3cb4851Tyler Luu // check if it is main jpeg thread 1628e52e3bcc31f65a699c25557cf3026d324e631b4Tyler Luu if(mThumb.get()) { 163c160a1f85c70e49a7613d774e2d99035f3cb4851Tyler Luu // wait until tn jpeg thread exits. 1648e52e3bcc31f65a699c25557cf3026d324e631b4Tyler Luu mThumb->join(); 1658e52e3bcc31f65a699c25557cf3026d324e631b4Tyler Luu mThumb.clear(); 1668e52e3bcc31f65a699c25557cf3026d324e631b4Tyler Luu mThumb = NULL; 167c160a1f85c70e49a7613d774e2d99035f3cb4851Tyler Luu } 168c11c07d676f130e6e28ab1611f4862a01a160389Tyler Luu 169c11c07d676f130e6e28ab1611f4862a01a160389Tyler Luu if(mCb) { 1700c7ae887ee41852f275887fd543fae810668e2d1Tyler Luu mCb(mMainInput, mThumbnailInput, mType, mCookie1, mCookie2, mCookie3, mCancelEncoding); 171c11c07d676f130e6e28ab1611f4862a01a160389Tyler Luu } 172c11c07d676f130e6e28ab1611f4862a01a160389Tyler Luu 173ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu // encoder thread runs, self-destructs, and then exits 174ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu this->decStrong(this); 175ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu return false; 176ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu } 177ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu 1788e52e3bcc31f65a699c25557cf3026d324e631b4Tyler Luu void cancel() { 1790c7ae887ee41852f275887fd543fae810668e2d1Tyler Luu mCancelEncoding = true; 1808e52e3bcc31f65a699c25557cf3026d324e631b4Tyler Luu if (mThumb.get()) { 1818e52e3bcc31f65a699c25557cf3026d324e631b4Tyler Luu mThumb->cancel(); 1820c7ae887ee41852f275887fd543fae810668e2d1Tyler Luu mCancelSem.WaitTimeout(CANCEL_TIMEOUT); 1838e52e3bcc31f65a699c25557cf3026d324e631b4Tyler Luu } 1840c7ae887ee41852f275887fd543fae810668e2d1Tyler Luu } 1850c7ae887ee41852f275887fd543fae810668e2d1Tyler Luu 1860c7ae887ee41852f275887fd543fae810668e2d1Tyler Luu void getCookies(void **cookie1, void **cookie2, void **cookie3) { 1870c7ae887ee41852f275887fd543fae810668e2d1Tyler Luu if (cookie1) *cookie1 = mCookie1; 1880c7ae887ee41852f275887fd543fae810668e2d1Tyler Luu if (cookie2) *cookie2 = mCookie2; 1890c7ae887ee41852f275887fd543fae810668e2d1Tyler Luu if (cookie3) *cookie3 = mCookie3; 1908e52e3bcc31f65a699c25557cf3026d324e631b4Tyler Luu } 1918e52e3bcc31f65a699c25557cf3026d324e631b4Tyler Luu 192ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu private: 193c160a1f85c70e49a7613d774e2d99035f3cb4851Tyler Luu params* mMainInput; 194c160a1f85c70e49a7613d774e2d99035f3cb4851Tyler Luu params* mThumbnailInput; 195ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu encoder_libjpeg_callback_t mCb; 1968e52e3bcc31f65a699c25557cf3026d324e631b4Tyler Luu bool mCancelEncoding; 197ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu void* mCookie1; 198ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu void* mCookie2; 199ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu void* mCookie3; 200ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu CameraFrame::FrameType mType; 2018e52e3bcc31f65a699c25557cf3026d324e631b4Tyler Luu sp<Encoder_libjpeg> mThumb; 2020c7ae887ee41852f275887fd543fae810668e2d1Tyler Luu Semaphore mCancelSem; 203ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu 204c160a1f85c70e49a7613d774e2d99035f3cb4851Tyler Luu size_t encode(params*); 205ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu}; 206ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu 207ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu} 208ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu 209ee6bb64f60c228d711dc1d6875d8f4b0ed88b6cfTyler Luu#endif 210