├── .gitmodules ├── CMakeLists.txt ├── README.md └── heif2jpeg.cpp /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "heif"] 2 | path = heif 3 | url = https://github.com/nokiatech/heif.git 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.3.2 FATAL_ERROR) 2 | 3 | set(HEIF2JPEG_EXE heic2jpeg) 4 | set(HEIF2JPEG_SRCS heif2jpeg.cpp) 5 | 6 | INCLUDE_DIRECTORIES(./heif/srcs/api/reader) 7 | INCLUDE_DIRECTORIES(./heif/srcs/api/common) 8 | LINK_DIRECTORIES(./heif/build/lib) 9 | add_executable(${HEIF2JPEG_EXE} ${HEIF2JPEG_SRCS}) 10 | 11 | target_link_libraries(${HEIF2JPEG_EXE} heif_static) 12 | target_link_libraries(${HEIF2JPEG_EXE} avcodec avutil swscale) 13 | 14 | 15 | if(UNIX) 16 | INCLUDE_DIRECTORIES(/usr/include/glib-2.0) # glib-object.h 17 | INCLUDE_DIRECTORIES(/usr/lib/x86_64-linux-gnu/glib-2.0/include/) # glibconfig.h 18 | target_link_libraries(${HEIF2JPEG_EXE} vips-cpp vips gobject-2.0 glib-2.0 jpeg png) 19 | else(MSYS OR MINGW) 20 | INCLUDE_DIRECTORIES(C:/msys64/mingw64/include/glib-2.0) 21 | target_link_libraries(${HEIF2JPEG_EXE} vips-cpp vips gobject-2.0 glib-2.0 gmodule-2.0 intl expat jpeg png) 22 | endif() 23 | 24 | 25 | set_property(TARGET ${HEIF2JPEG_EXE} PROPERTY CXX_STANDARD 11) 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Convert heif to jpeg/png 2 | **About project**: 3 | 4 | based on `nokiatech/heif`: https://github.com/nokiatech/heif 5 | Inspired by @yohhoy https://github.com/yohhoy/heic2hevc 6 | 7 | When I had really pulled my hair out that I didn't know how to deal with iPhone heic file,the following library helped me. 8 | https://github.com/monostream/tifig 9 | I use the code in my project. 10 | 11 | This project is in developing, and it needs some tweak. 12 | 13 | **Knowledge about HEIF format(written in Chinese)**: 14 | [HEIF格式详细解读](https://www.crb912.top/computer%20science/2018/05/01/HEIF-format.html) 15 | 16 | --- 17 | ## Part Ⅰ: Dependencies 18 | ### 1. glib2.0-dev 19 | Since `libvips` has to have `glib2.0-dev`, you must make sure that you have installed it. If you had done it, you could circumvent the step. 20 | #### How to install`glib2.0-dev` ? 21 | **ubuntu**: 22 | ``` 23 | sudo apt-get install libglib2.0-dev 24 | ``` 25 | **MSYS**: 26 | ``` 27 | pacman -S glib2-devel 28 | pacman -S glib2 2.48.2-1 29 | ``` 30 | 31 | ### 2. libvips 32 | libvips is a 2D image processing library. I use the library for generating JPEG and PNG, including cropping tiles of iPhone heic file. 33 | https://github.com/jcupitt/libvips 34 | #### 2.1 Download Link 35 | https://github.com/jcupitt/libvips/releases 36 | Recommended version: vips 8.6.3 37 | #### 2.2 Building libvips 38 | To aviod using many dependencies, you'd better configure with the following arguments. 39 | 40 | ``` 41 | ./configure --enable-debug=[yes] --enable-static --without-gsf \ 42 | --without-fftw --without-magick --without-orc --without-lcms --without-OpenEXR \ 43 | --without-poppler --without-rsvg --without-zlib --without-openslide --without-matio \ 44 | --without-ppm --without-analyze --without-radiance --without-cfitsio --without-libwebp\ 45 | --without-pangoft2 --without-tiff --without-giflib --without-libexif 46 | ``` 47 | Check the summary at the end of `configure` carefully! 48 | Check the summary at the end of `configure` carefully!! 49 | Check the summary at the end of `configure` carefully!!! 50 | 51 | When ending of `configure`, checking and make sure: 52 | ``` 53 | file import/export with libpng: yes (pkg-config libpng >= 1.2.9) 54 | file import/export with libjpeg: yes (pkg-config) 55 | ``` 56 | 57 | **Error handle 1**: lack of `Expat library` 58 | If you don't meet the problem, please skip. 59 | ``` 60 | checking for XML_ParserCreate in -lexpat... no 61 | configure: error: Could not find the 62 | ``` 63 | Error Source:libvips must have `libexpat1-dev`. So: 64 | ``` 65 | sudo apt install libexpat1-dev // Ubuntu 66 | ``` 67 | 68 | **Error handle 2**: lack of `libpng`,`libjpeg` 69 | ``` 70 | file import/export with libpng: no 71 | file import/export with libjpeg: no // or find by search 72 | ``` 73 | If you meet the problem, you must install them. For Ubuntu: 74 | 75 | ``` 76 | sudo apt install libpng16-dev 77 | sudo apt install libjpeg-dev 78 | ``` 79 | 80 | ##### Continue 81 | Once `configure` is looking OK, compile and install with the usual: 82 | By default this will install files to `/usr/local`. 83 | ``` 84 | make 85 | sudo make install 86 | ``` 87 | 88 | ### 3. FFmpeg 89 | ffmpeg: https://www.ffmpeg.org/ 90 | FFmpeg library support encoding and decoding of HEVC(H.254). 91 | Check: 92 | - `libavcodec` >=3.1 93 | - `libswscale` >=3.1 94 | 95 | **Install or update FFmpeg library**: 96 | Ubuntu 97 | ```markdown 98 | sudo apt install libavcodec-dev 99 | sudo apt install libswscale-dev 100 | ``` 101 | 102 | Msys + mingw64 103 | ``` 104 | pacman -S mingw-w64-x86_64-ffmpeg 105 | ``` 106 | 107 | If you can't install FFmpeg with the above method, you should build FFmpeg source code. 108 | 109 | FFmpeg Download Link:https://www.ffmpeg.org/download.html#releases 110 | Recommand Version:FFmpeg 3.4.2 "Cantor" 111 | 112 | ## Part Ⅱ: Build 113 | 1. At first, build Nokia heif library. 114 | ``` 115 | cd heif/build 116 | cmake ../srcs 117 | cmake --build . 118 | ``` 119 | 2. Waiting a few minutes,then 120 | ``` 121 | cd .. 122 | cd .. 123 | ls // CMakeLists.txt heif heif2jpeg.cpp READNE.md 124 | mkdir build && cd build 125 | cmake .. 126 | cmake --build . 127 | ``` 128 | Check executable file(/build). 129 | 130 | **Error handle 1**: can't find `glib-object.h` or `glibconfig.h` 131 | The cause of the error is compiler don't have find the header file('glib2.0'). 132 | Check file: 133 | `/srcs/example/CmakeList.txt` 134 | ``` 135 | INCLUDE_DIRECTORIES(right path) # glib-object.h 136 | ``` 137 | 138 | **Example**(ubuntu 16.04): 139 | ``` 140 | INCLUDE_DIRECTORIES(/usr/include/glib-2.0) # glib-object.h 141 | ``` 142 | 143 | **Error handle 2**: undefined reference 144 | I think you may not follow the guide build step by step, so that some additional dependencies are needed. Re-build or add those dependencies by yourself. 145 | 146 | ## Part Ⅲ: Usages 147 | When compiler success,there is a executable file `heic2jpg` in `/build/`. 148 | 149 | ``` 150 | ./heic2jpg inputFileName.heic outputFileName.jpeg 151 | ``` 152 | If you want to support more picture format, please change `libvips` configure arguments. 153 | 154 | **Error handle**: `libvips-cpp.so.42` 155 | ``` 156 | ./heic2jpeg: error while loading shared libraries: libvips-cpp.so.42: 157 | cannot open shared object file: No such file or directory 158 | ``` 159 | update your ldconfig cache with this command: 160 | ``` 161 | sudo ldconfig 162 | ``` 163 | 164 | **Suggestions for improvements are highly welcome!** 165 | -------------------------------------------------------------------------------- /heif2jpeg.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "heifreader.h" 4 | #include 5 | extern "C" { 6 | #include 7 | #include 8 | #include 9 | } 10 | 11 | using namespace std; 12 | using namespace HEIF; 13 | using namespace vips; 14 | void haveThumb(struct FileInformation fileInfo); 15 | int iphoneHeic(HEIF_DLL_PUBLIC Reader* reader, Grid gridData, const char* filename); 16 | AVCodecContext* getHEVCDecoderContext(); 17 | 18 | static struct SwsContext* swsContext; 19 | struct rgbData 20 | { 21 | uint8_t* data; 22 | size_t size; 23 | int width; 24 | int height; 25 | }; 26 | 27 | int main(int argc, char *argv[]) 28 | { 29 | if (argc < 3) 30 | { 31 | cout << "Usage: ./heif2jpeg " << endl; 32 | return 0; 33 | } 34 | const char* inputFileName = argv[1]; 35 | const char* outputFileName = argv[2]; 36 | 37 | // Make an instance of Heif Reader 38 | auto *reader = Reader::Create(); 39 | reader->initialize(inputFileName); 40 | 41 | // Verify that the file is HEIF format. 42 | FileInformation fileInfo; 43 | if (reader->getFileInformation(fileInfo) != ErrorCode::OK) { 44 | cout << "Unable to get MetaBox! Wrong heif format." << endl; 45 | return 0; 46 | } 47 | if (!(fileInfo.features & FileFeatureEnum::HasSingleImage || 48 | fileInfo.features & FileFeatureEnum::HasImageCollection)) { 49 | cout << "The file don't have images in the Metabox. " << endl; 50 | return 0; 51 | } 52 | 53 | haveThumb(fileInfo); 54 | 55 | // when the file is iPhone heic. 56 | Array gridIds; 57 | Grid gridData; 58 | if (reader->getItemListByType("grid",gridIds) == ErrorCode::OK && 59 | (fileInfo.features & FileFeatureEnum::HasImageCollection) && 60 | reader->getItem(gridIds[0].get(), gridData) == ErrorCode::OK) 61 | { 62 | return iphoneHeic(reader, gridData, outputFileName); 63 | } 64 | 65 | // The image have collections 66 | uint64_t memoryBufferSize = 1024 * 1024; 67 | ofstream outfile(outputFileName, ofstream::binary); 68 | auto *memoryBuffer = new uint8_t[memoryBufferSize]; 69 | if (fileInfo.features & FileFeatureEnum::HasImageCollection) { 70 | Array itemIds; 71 | reader->getMasterImages(itemIds); 72 | // all the image items 73 | for (unsigned int i = 0; i < itemIds.size; i++) { 74 | const ImageId masterId = itemIds[i]; 75 | if (reader->getItemDataWithDecoderParameters( 76 | masterId.get(), memoryBuffer, memoryBufferSize) == ErrorCode::OK) { 77 | DecoderConfiguration decodeConf; // struct containing 78 | reader->getDecoderParameterSets(masterId.get(), decodeConf); 79 | auto decoSpeInfo = decodeConf.decoderSpecificInfo; 80 | 81 | for (unsigned int j = 0; j < decoSpeInfo.size; ++j) { 82 | auto entry = decoSpeInfo[j]; 83 | outfile.write((const char *) entry.decSpecInfoData.begin(), 84 | entry.decSpecInfoData.size); 85 | } 86 | outfile.write(reinterpret_cast(memoryBuffer), memoryBufferSize); 87 | } else { cout << "getItemDataWithDecoderParameters error" << endl; } 88 | }; 89 | delete[] memoryBuffer; 90 | Reader::Destroy(reader); 91 | return 0; 92 | } 93 | 94 | // The image only have 1 master image. 95 | else if (fileInfo.features & FileFeatureEnum::HasSingleImage) { 96 | Array itemIds; 97 | reader->getMasterImages(itemIds); 98 | 99 | // Find the item ID of the first master image 100 | const ImageId masterId = itemIds[0]; 101 | if (reader->getItemDataWithDecoderParameters(masterId.get(), memoryBuffer, memoryBufferSize) == ErrorCode::OK) { 102 | outfile.write(reinterpret_cast(memoryBuffer), memoryBufferSize); 103 | cout << "Get the master image" << endl; 104 | delete[] memoryBuffer; 105 | } 106 | Reader::Destroy(reader); 107 | return 0; 108 | } 109 | cout << "There no image in the file MetaBox! "; 110 | Reader::Destroy(reader); 111 | return 0; 112 | } 113 | 114 | 115 | // Verify that the file have Thumbnail. 116 | void haveThumb(FileInformation fileInfo) 117 | { 118 | const auto metaBoxFeatures = fileInfo.rootMetaBoxInformation.features; 119 | if (metaBoxFeatures & MetaBoxFeatureEnum::HasThumbnails) 120 | { 121 | cout << "The file have Thumbnail." << endl; 122 | } 123 | } 124 | 125 | int iphoneHeic(HEIF_DLL_PUBLIC Reader* reader, Grid gridData, const char* outputFileName) 126 | { 127 | Array tileItemIds; 128 | cout << "Width: " << gridData.outputWidth << ",Height: " << gridData.outputHeight << endl; 129 | cout << "Columns: "<< gridData.columns << ",Row: " << gridData.rows << endl; 130 | tileItemIds = gridData.imageIds; 131 | cout << "Tiles nums:" << tileItemIds.size << endl; 132 | // tiles container 133 | vector tiles; 134 | 135 | for (auto tileItemId : tileItemIds) 136 | { 137 | avcodec_register_all(); 138 | uint64_t memoryBufferSize = 1024 * 1024; 139 | auto *memoryBuffer = new uint8_t[memoryBufferSize]; 140 | 141 | if (reader->getItemDataWithDecoderParameters(tileItemId.get(), 142 | memoryBuffer, memoryBufferSize) ==ErrorCode::OK) 143 | { 144 | // Get HEVC decoder configuration 145 | AVCodecContext* c = getHEVCDecoderContext(); 146 | AVFrame* frame = av_frame_alloc(); 147 | AVPacket* packet = av_packet_alloc(); 148 | 149 | packet->size = static_cast(memoryBufferSize); 150 | packet->data = ((uint8_t*)(&memoryBuffer[0])); 151 | auto* errorDescription = new char[256]; 152 | 153 | avcodec_register_all(); 154 | // handle error! 155 | int sent = avcodec_send_packet(c, packet); 156 | if (sent < 0) 157 | { 158 | av_strerror(sent, errorDescription, 256); 159 | cerr << "Error sending packet to HEVC decoder: " << errorDescription << endl; 160 | throw sent; 161 | } 162 | int success = avcodec_receive_frame(c, frame); 163 | if (success != 0) 164 | { 165 | av_strerror(success, errorDescription, 256); 166 | cerr << "Error decoding frame: " << errorDescription << endl; 167 | throw success; 168 | } 169 | delete[] errorDescription; 170 | size_t bufferSize = static_cast(av_image_get_buffer_size( 171 | AV_PIX_FMT_RGB24, frame->width, frame->height, 1)); 172 | 173 | 174 | struct rgbData rgb = 175 | { 176 | .data = (uint8_t *) malloc(bufferSize), 177 | .size = bufferSize, 178 | .width = frame->width, 179 | .height = frame->height, 180 | }; 181 | 182 | // Convert colorspace of decoded frame load into buffer 183 | // copyFrameInto(frame, rgb.data, rgb.size); 184 | AVFrame* imgFrame = av_frame_alloc(); 185 | auto tempBuffer = (uint8_t*) av_malloc(rgb.size); 186 | struct SwsContext *sws_ctx = sws_getCachedContext( 187 | swsContext, 188 | rgb.width, rgb.height, AV_PIX_FMT_YUV420P, 189 | rgb.width, rgb.height, AV_PIX_FMT_RGB24, 190 | 0, nullptr, nullptr, nullptr); 191 | 192 | av_image_fill_arrays(imgFrame->data, imgFrame->linesize, 193 | tempBuffer, AV_PIX_FMT_RGB24, rgb.width, rgb.height, 1); 194 | auto* const* frameDataPtr = (uint8_t const* const*)frame->data; 195 | 196 | // Convert YUV to RGB 197 | sws_scale(sws_ctx, frameDataPtr, frame->linesize, 0, 198 | rgb.height, imgFrame->data, imgFrame->linesize); 199 | 200 | // Move RGB data in pixel order into memory 201 | auto dataPtr = static_cast(imgFrame->data); 202 | auto size = static_cast(rgb.size); 203 | int ret = av_image_copy_to_buffer(rgb.data, size, dataPtr, 204 | imgFrame->linesize, AV_PIX_FMT_RGB24, rgb.width, rgb.height, 1); 205 | 206 | av_free(imgFrame); 207 | av_free(tempBuffer); 208 | avcodec_close(c); 209 | av_free(c); 210 | av_free(frame); 211 | 212 | vips::VImage imgTile = vips::VImage::new_from_memory(rgb.data, rgb.size, 213 | rgb.width, rgb.height, 3, VIPS_FORMAT_UCHAR); 214 | tiles.push_back(imgTile); 215 | } 216 | delete[] memoryBuffer; 217 | } 218 | 219 | // Stitch tiles together 220 | vips::VImage iphonePic = vips::VImage::new_memory(); 221 | iphonePic = iphonePic.arrayjoin(tiles, vips::VImage::option()->set("across", (int)gridData.columns)); 222 | iphonePic = iphonePic.extract_area(0, 0, gridData.outputWidth, gridData.outputHeight); 223 | 224 | // Write out 225 | VipsBlob* jpegBuffer = iphonePic.jpegsave_buffer(vips:: VImage::option()->set("Q", 90)); 226 | ofstream outfile(outputFileName, ofstream::binary); 227 | outfile.write(static_cast(jpegBuffer->area.data), jpegBuffer->area.length); 228 | 229 | cout << ":) Congratulations! Finished." << endl; 230 | Reader::Destroy(reader); 231 | return 0; 232 | } 233 | 234 | // Verify that the heif file is encoded with HEVC. 235 | AVCodecContext* getHEVCDecoderContext() 236 | { 237 | AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_HEVC); 238 | if (codec == NULL) 239 | { 240 | throw logic_error("avcodec_find_decoder retun NULL"); 241 | } 242 | AVCodecContext *c = avcodec_alloc_context3(codec); 243 | 244 | if (!c) { 245 | throw logic_error("Could not allocate video codec context"); 246 | } 247 | 248 | if (avcodec_open2(c, c->codec, nullptr) < 0) { 249 | throw logic_error("Could not open codec"); 250 | } 251 | return c; 252 | } 253 | --------------------------------------------------------------------------------