12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864 |
- //
- // YYImageCoder.m
- // YYKit <https://github.com/ibireme/YYKit>
- //
- // Created by ibireme on 15/5/13.
- // Copyright (c) 2015 ibireme.
- //
- // This source code is licensed under the MIT-style license found in the
- // LICENSE file in the root directory of this source tree.
- //
- #import "YYImageCoder.h"
- #import <CoreFoundation/CoreFoundation.h>
- #import <ImageIO/ImageIO.h>
- #import <Accelerate/Accelerate.h>
- #import <QuartzCore/QuartzCore.h>
- #import <MobileCoreServices/MobileCoreServices.h>
- #import <AssetsLibrary/AssetsLibrary.h>
- #import <objc/runtime.h>
- #import <pthread.h>
- #import <zlib.h>
- #import "YYImage.h"
- #import "YYKitMacro.h"
- #ifndef YYIMAGE_WEBP_ENABLED
- #if __has_include(<webp/decode.h>) && __has_include(<webp/encode.h>) && \
- __has_include(<webp/demux.h>) && __has_include(<webp/mux.h>)
- #define YYIMAGE_WEBP_ENABLED 1
- #import <webp/decode.h>
- #import <webp/encode.h>
- #import <webp/demux.h>
- #import <webp/mux.h>
- #elif __has_include("webp/decode.h") && __has_include("webp/encode.h") && \
- __has_include("webp/demux.h") && __has_include("webp/mux.h")
- #define YYIMAGE_WEBP_ENABLED 1
- #import "webp/decode.h"
- #import "webp/encode.h"
- #import "webp/demux.h"
- #import "webp/mux.h"
- #else
- #define YYIMAGE_WEBP_ENABLED 0
- #endif
- #endif
- ////////////////////////////////////////////////////////////////////////////////
- #pragma mark - Utility (for little endian platform)
- #define YY_FOUR_CC(c1,c2,c3,c4) ((uint32_t)(((c4) << 24) | ((c3) << 16) | ((c2) << 8) | (c1)))
- #define YY_TWO_CC(c1,c2) ((uint16_t)(((c2) << 8) | (c1)))
- static inline uint16_t yy_swap_endian_uint16(uint16_t value) {
- return
- (uint16_t) ((value & 0x00FF) << 8) |
- (uint16_t) ((value & 0xFF00) >> 8) ;
- }
- static inline uint32_t yy_swap_endian_uint32(uint32_t value) {
- return
- (uint32_t)((value & 0x000000FFU) << 24) |
- (uint32_t)((value & 0x0000FF00U) << 8) |
- (uint32_t)((value & 0x00FF0000U) >> 8) |
- (uint32_t)((value & 0xFF000000U) >> 24) ;
- }
- ////////////////////////////////////////////////////////////////////////////////
- #pragma mark - APNG
- /*
- PNG spec: http://www.libpng.org/pub/png/spec/1.2/PNG-Structure.html
- APNG spec: https://wiki.mozilla.org/APNG_Specification
-
- ===============================================================================
- PNG format:
- header (8): 89 50 4e 47 0d 0a 1a 0a
- chunk, chunk, chunk, ...
-
- ===============================================================================
- chunk format:
- length (4): uint32_t big endian
- fourcc (4): chunk type code
- data (length): data
- crc32 (4): uint32_t big endian crc32(fourcc + data)
-
- ===============================================================================
- PNG chunk define:
-
- IHDR (Image Header) required, must appear first, 13 bytes
- width (4) pixel count, should not be zero
- height (4) pixel count, should not be zero
- bit depth (1) expected: 1, 2, 4, 8, 16
- color type (1) 1<<0 (palette used), 1<<1 (color used), 1<<2 (alpha channel used)
- compression method (1) 0 (deflate/inflate)
- filter method (1) 0 (adaptive filtering with five basic filter types)
- interlace method (1) 0 (no interlace) or 1 (Adam7 interlace)
-
- IDAT (Image Data) required, must appear consecutively if there's multiple 'IDAT' chunk
-
- IEND (End) required, must appear last, 0 bytes
-
- ===============================================================================
- APNG chunk define:
-
- acTL (Animation Control) required, must appear before 'IDAT', 8 bytes
- num frames (4) number of frames
- num plays (4) number of times to loop, 0 indicates infinite looping
-
- fcTL (Frame Control) required, must appear before the 'IDAT' or 'fdAT' chunks of the frame to which it applies, 26 bytes
- sequence number (4) sequence number of the animation chunk, starting from 0
- width (4) width of the following frame
- height (4) height of the following frame
- x offset (4) x position at which to render the following frame
- y offset (4) y position at which to render the following frame
- delay num (2) frame delay fraction numerator
- delay den (2) frame delay fraction denominator
- dispose op (1) type of frame area disposal to be done after rendering this frame (0:none, 1:background 2:previous)
- blend op (1) type of frame area rendering for this frame (0:source, 1:over)
-
- fdAT (Frame Data) required
- sequence number (4) sequence number of the animation chunk
- frame data (x) frame data for this frame (same as 'IDAT')
-
- ===============================================================================
- `dispose_op` specifies how the output buffer should be changed at the end of the delay
- (before rendering the next frame).
-
- * NONE: no disposal is done on this frame before rendering the next; the contents
- of the output buffer are left as is.
- * BACKGROUND: the frame's region of the output buffer is to be cleared to fully
- transparent black before rendering the next frame.
- * PREVIOUS: the frame's region of the output buffer is to be reverted to the previous
- contents before rendering the next frame.
- `blend_op` specifies whether the frame is to be alpha blended into the current output buffer
- content, or whether it should completely replace its region in the output buffer.
-
- * SOURCE: all color components of the frame, including alpha, overwrite the current contents
- of the frame's output buffer region.
- * OVER: the frame should be composited onto the output buffer based on its alpha,
- using a simple OVER operation as described in the "Alpha Channel Processing" section
- of the PNG specification
- */
- typedef enum {
- YY_PNG_ALPHA_TYPE_PALEETE = 1 << 0,
- YY_PNG_ALPHA_TYPE_COLOR = 1 << 1,
- YY_PNG_ALPHA_TYPE_ALPHA = 1 << 2,
- } yy_png_alpha_type;
- typedef enum {
- YY_PNG_DISPOSE_OP_NONE = 0,
- YY_PNG_DISPOSE_OP_BACKGROUND = 1,
- YY_PNG_DISPOSE_OP_PREVIOUS = 2,
- } yy_png_dispose_op;
- typedef enum {
- YY_PNG_BLEND_OP_SOURCE = 0,
- YY_PNG_BLEND_OP_OVER = 1,
- } yy_png_blend_op;
- typedef struct {
- uint32_t width; ///< pixel count, should not be zero
- uint32_t height; ///< pixel count, should not be zero
- uint8_t bit_depth; ///< expected: 1, 2, 4, 8, 16
- uint8_t color_type; ///< see yy_png_alpha_type
- uint8_t compression_method; ///< 0 (deflate/inflate)
- uint8_t filter_method; ///< 0 (adaptive filtering with five basic filter types)
- uint8_t interlace_method; ///< 0 (no interlace) or 1 (Adam7 interlace)
- } yy_png_chunk_IHDR;
- typedef struct {
- uint32_t sequence_number; ///< sequence number of the animation chunk, starting from 0
- uint32_t width; ///< width of the following frame
- uint32_t height; ///< height of the following frame
- uint32_t x_offset; ///< x position at which to render the following frame
- uint32_t y_offset; ///< y position at which to render the following frame
- uint16_t delay_num; ///< frame delay fraction numerator
- uint16_t delay_den; ///< frame delay fraction denominator
- uint8_t dispose_op; ///< see yy_png_dispose_op
- uint8_t blend_op; ///< see yy_png_blend_op
- } yy_png_chunk_fcTL;
- typedef struct {
- uint32_t offset; ///< chunk offset in PNG data
- uint32_t fourcc; ///< chunk fourcc
- uint32_t length; ///< chunk data length
- uint32_t crc32; ///< chunk crc32
- } yy_png_chunk_info;
- typedef struct {
- uint32_t chunk_index; ///< the first `fdAT`/`IDAT` chunk index
- uint32_t chunk_num; ///< the `fdAT`/`IDAT` chunk count
- uint32_t chunk_size; ///< the `fdAT`/`IDAT` chunk bytes
- yy_png_chunk_fcTL frame_control;
- } yy_png_frame_info;
- typedef struct {
- yy_png_chunk_IHDR header; ///< png header
- yy_png_chunk_info *chunks; ///< chunks
- uint32_t chunk_num; ///< count of chunks
-
- yy_png_frame_info *apng_frames; ///< frame info, NULL if not apng
- uint32_t apng_frame_num; ///< 0 if not apng
- uint32_t apng_loop_num; ///< 0 indicates infinite looping
-
- uint32_t *apng_shared_chunk_indexs; ///< shared chunk index
- uint32_t apng_shared_chunk_num; ///< shared chunk count
- uint32_t apng_shared_chunk_size; ///< shared chunk bytes
- uint32_t apng_shared_insert_index; ///< shared chunk insert index
- bool apng_first_frame_is_cover; ///< the first frame is same as png (cover)
- } yy_png_info;
- static void yy_png_chunk_IHDR_read(yy_png_chunk_IHDR *IHDR, const uint8_t *data) {
- IHDR->width = yy_swap_endian_uint32(*((uint32_t *)(data)));
- IHDR->height = yy_swap_endian_uint32(*((uint32_t *)(data + 4)));
- IHDR->bit_depth = data[8];
- IHDR->color_type = data[9];
- IHDR->compression_method = data[10];
- IHDR->filter_method = data[11];
- IHDR->interlace_method = data[12];
- }
- static void yy_png_chunk_IHDR_write(yy_png_chunk_IHDR *IHDR, uint8_t *data) {
- *((uint32_t *)(data)) = yy_swap_endian_uint32(IHDR->width);
- *((uint32_t *)(data + 4)) = yy_swap_endian_uint32(IHDR->height);
- data[8] = IHDR->bit_depth;
- data[9] = IHDR->color_type;
- data[10] = IHDR->compression_method;
- data[11] = IHDR->filter_method;
- data[12] = IHDR->interlace_method;
- }
- static void yy_png_chunk_fcTL_read(yy_png_chunk_fcTL *fcTL, const uint8_t *data) {
- fcTL->sequence_number = yy_swap_endian_uint32(*((uint32_t *)(data)));
- fcTL->width = yy_swap_endian_uint32(*((uint32_t *)(data + 4)));
- fcTL->height = yy_swap_endian_uint32(*((uint32_t *)(data + 8)));
- fcTL->x_offset = yy_swap_endian_uint32(*((uint32_t *)(data + 12)));
- fcTL->y_offset = yy_swap_endian_uint32(*((uint32_t *)(data + 16)));
- fcTL->delay_num = yy_swap_endian_uint16(*((uint16_t *)(data + 20)));
- fcTL->delay_den = yy_swap_endian_uint16(*((uint16_t *)(data + 22)));
- fcTL->dispose_op = data[24];
- fcTL->blend_op = data[25];
- }
- static void yy_png_chunk_fcTL_write(yy_png_chunk_fcTL *fcTL, uint8_t *data) {
- *((uint32_t *)(data)) = yy_swap_endian_uint32(fcTL->sequence_number);
- *((uint32_t *)(data + 4)) = yy_swap_endian_uint32(fcTL->width);
- *((uint32_t *)(data + 8)) = yy_swap_endian_uint32(fcTL->height);
- *((uint32_t *)(data + 12)) = yy_swap_endian_uint32(fcTL->x_offset);
- *((uint32_t *)(data + 16)) = yy_swap_endian_uint32(fcTL->y_offset);
- *((uint16_t *)(data + 20)) = yy_swap_endian_uint16(fcTL->delay_num);
- *((uint16_t *)(data + 22)) = yy_swap_endian_uint16(fcTL->delay_den);
- data[24] = fcTL->dispose_op;
- data[25] = fcTL->blend_op;
- }
- // convert double value to fraction
- static void yy_png_delay_to_fraction(double duration, uint16_t *num, uint16_t *den) {
- if (duration >= 0xFF) {
- *num = 0xFF;
- *den = 1;
- } else if (duration <= 1.0 / (double)0xFF) {
- *num = 1;
- *den = 0xFF;
- } else {
- // Use continued fraction to calculate the num and den.
- long MAX = 10;
- double eps = (0.5 / (double)0xFF);
- long p[MAX], q[MAX], a[MAX], i, numl = 0, denl = 0;
- // The first two convergents are 0/1 and 1/0
- p[0] = 0; q[0] = 1;
- p[1] = 1; q[1] = 0;
- // The rest of the convergents (and continued fraction)
- for (i = 2; i < MAX; i++) {
- a[i] = lrint(floor(duration));
- p[i] = a[i] * p[i - 1] + p[i - 2];
- q[i] = a[i] * q[i - 1] + q[i - 2];
- if (p[i] <= 0xFF && q[i] <= 0xFF) { // uint16_t
- numl = p[i];
- denl = q[i];
- } else break;
- if (fabs(duration - a[i]) < eps) break;
- duration = 1.0 / (duration - a[i]);
- }
-
- if (numl != 0 && denl != 0) {
- *num = numl;
- *den = denl;
- } else {
- *num = 1;
- *den = 100;
- }
- }
- }
- // convert fraction to double value
- static double yy_png_delay_to_seconds(uint16_t num, uint16_t den) {
- if (den == 0) {
- return num / 100.0;
- } else {
- return (double)num / (double)den;
- }
- }
- static bool yy_png_validate_animation_chunk_order(yy_png_chunk_info *chunks, /* input */
- uint32_t chunk_num, /* input */
- uint32_t *first_idat_index, /* output */
- bool *first_frame_is_cover /* output */) {
- /*
- PNG at least contains 3 chunks: IHDR, IDAT, IEND.
- `IHDR` must appear first.
- `IDAT` must appear consecutively.
- `IEND` must appear end.
-
- APNG must contains one `acTL` and at least one 'fcTL' and `fdAT`.
- `fdAT` must appear consecutively.
- `fcTL` must appear before `IDAT` or `fdAT`.
- */
- if (chunk_num <= 2) return false;
- if (chunks->fourcc != YY_FOUR_CC('I', 'H', 'D', 'R')) return false;
- if ((chunks + chunk_num - 1)->fourcc != YY_FOUR_CC('I', 'E', 'N', 'D')) return false;
-
- uint32_t prev_fourcc = 0;
- uint32_t IHDR_num = 0;
- uint32_t IDAT_num = 0;
- uint32_t acTL_num = 0;
- uint32_t fcTL_num = 0;
- uint32_t first_IDAT = 0;
- bool first_frame_cover = false;
- for (uint32_t i = 0; i < chunk_num; i++) {
- yy_png_chunk_info *chunk = chunks + i;
- switch (chunk->fourcc) {
- case YY_FOUR_CC('I', 'H', 'D', 'R'): { // png header
- if (i != 0) return false;
- if (IHDR_num > 0) return false;
- IHDR_num++;
- } break;
- case YY_FOUR_CC('I', 'D', 'A', 'T'): { // png data
- if (prev_fourcc != YY_FOUR_CC('I', 'D', 'A', 'T')) {
- if (IDAT_num == 0)
- first_IDAT = i;
- else
- return false;
- }
- IDAT_num++;
- } break;
- case YY_FOUR_CC('a', 'c', 'T', 'L'): { // apng control
- if (acTL_num > 0) return false;
- acTL_num++;
- } break;
- case YY_FOUR_CC('f', 'c', 'T', 'L'): { // apng frame control
- if (i + 1 == chunk_num) return false;
- if ((chunk + 1)->fourcc != YY_FOUR_CC('f', 'd', 'A', 'T') &&
- (chunk + 1)->fourcc != YY_FOUR_CC('I', 'D', 'A', 'T')) {
- return false;
- }
- if (fcTL_num == 0) {
- if ((chunk + 1)->fourcc == YY_FOUR_CC('I', 'D', 'A', 'T')) {
- first_frame_cover = true;
- }
- }
- fcTL_num++;
- } break;
- case YY_FOUR_CC('f', 'd', 'A', 'T'): { // apng data
- if (prev_fourcc != YY_FOUR_CC('f', 'd', 'A', 'T') && prev_fourcc != YY_FOUR_CC('f', 'c', 'T', 'L')) {
- return false;
- }
- } break;
- }
- prev_fourcc = chunk->fourcc;
- }
- if (IHDR_num != 1) return false;
- if (IDAT_num == 0) return false;
- if (acTL_num != 1) return false;
- if (fcTL_num < acTL_num) return false;
- *first_idat_index = first_IDAT;
- *first_frame_is_cover = first_frame_cover;
- return true;
- }
- static void yy_png_info_release(yy_png_info *info) {
- if (info) {
- if (info->chunks) free(info->chunks);
- if (info->apng_frames) free(info->apng_frames);
- if (info->apng_shared_chunk_indexs) free(info->apng_shared_chunk_indexs);
- free(info);
- }
- }
- /**
- Create a png info from a png file. See struct png_info for more information.
-
- @param data png/apng file data.
- @param length the data's length in bytes.
- @return A png info object, you may call yy_png_info_release() to release it.
- Returns NULL if an error occurs.
- */
- static yy_png_info *yy_png_info_create(const uint8_t *data, uint32_t length) {
- if (length < 32) return NULL;
- if (*((uint32_t *)data) != YY_FOUR_CC(0x89, 0x50, 0x4E, 0x47)) return NULL;
- if (*((uint32_t *)(data + 4)) != YY_FOUR_CC(0x0D, 0x0A, 0x1A, 0x0A)) return NULL;
-
- uint32_t chunk_realloc_num = 16;
- yy_png_chunk_info *chunks = malloc(sizeof(yy_png_chunk_info) * chunk_realloc_num);
- if (!chunks) return NULL;
-
- // parse png chunks
- uint32_t offset = 8;
- uint32_t chunk_num = 0;
- uint32_t chunk_capacity = chunk_realloc_num;
- uint32_t apng_loop_num = 0;
- int32_t apng_sequence_index = -1;
- int32_t apng_frame_index = 0;
- int32_t apng_frame_number = -1;
- bool apng_chunk_error = false;
- do {
- if (chunk_num >= chunk_capacity) {
- yy_png_chunk_info *new_chunks = realloc(chunks, sizeof(yy_png_chunk_info) * (chunk_capacity + chunk_realloc_num));
- if (!new_chunks) {
- free(chunks);
- return NULL;
- }
- chunks = new_chunks;
- chunk_capacity += chunk_realloc_num;
- }
- yy_png_chunk_info *chunk = chunks + chunk_num;
- const uint8_t *chunk_data = data + offset;
- chunk->offset = offset;
- chunk->length = yy_swap_endian_uint32(*((uint32_t *)chunk_data));
- if ((uint64_t)chunk->offset + (uint64_t)chunk->length + 12 > length) {
- free(chunks);
- return NULL;
- }
-
- chunk->fourcc = *((uint32_t *)(chunk_data + 4));
- if ((uint64_t)chunk->offset + 4 + chunk->length + 4 > (uint64_t)length) break;
- chunk->crc32 = yy_swap_endian_uint32(*((uint32_t *)(chunk_data + 8 + chunk->length)));
- chunk_num++;
- offset += 12 + chunk->length;
-
- switch (chunk->fourcc) {
- case YY_FOUR_CC('a', 'c', 'T', 'L') : {
- if (chunk->length == 8) {
- apng_frame_number = yy_swap_endian_uint32(*((uint32_t *)(chunk_data + 8)));
- apng_loop_num = yy_swap_endian_uint32(*((uint32_t *)(chunk_data + 12)));
- } else {
- apng_chunk_error = true;
- }
- } break;
- case YY_FOUR_CC('f', 'c', 'T', 'L') :
- case YY_FOUR_CC('f', 'd', 'A', 'T') : {
- if (chunk->fourcc == YY_FOUR_CC('f', 'c', 'T', 'L')) {
- if (chunk->length != 26) {
- apng_chunk_error = true;
- } else {
- apng_frame_index++;
- }
- }
- if (chunk->length > 4) {
- uint32_t sequence = yy_swap_endian_uint32(*((uint32_t *)(chunk_data + 8)));
- if (apng_sequence_index + 1 == sequence) {
- apng_sequence_index++;
- } else {
- apng_chunk_error = true;
- }
- } else {
- apng_chunk_error = true;
- }
- } break;
- case YY_FOUR_CC('I', 'E', 'N', 'D') : {
- offset = length; // end, break do-while loop
- } break;
- }
- } while (offset + 12 <= length);
-
- if (chunk_num < 3 ||
- chunks->fourcc != YY_FOUR_CC('I', 'H', 'D', 'R') ||
- chunks->length != 13) {
- free(chunks);
- return NULL;
- }
-
- // png info
- yy_png_info *info = calloc(1, sizeof(yy_png_info));
- if (!info) {
- free(chunks);
- return NULL;
- }
- info->chunks = chunks;
- info->chunk_num = chunk_num;
- yy_png_chunk_IHDR_read(&info->header, data + chunks->offset + 8);
-
- // apng info
- if (!apng_chunk_error && apng_frame_number == apng_frame_index && apng_frame_number >= 1) {
- bool first_frame_is_cover = false;
- uint32_t first_IDAT_index = 0;
- if (!yy_png_validate_animation_chunk_order(info->chunks, info->chunk_num, &first_IDAT_index, &first_frame_is_cover)) {
- return info; // ignore apng chunk
- }
-
- info->apng_loop_num = apng_loop_num;
- info->apng_frame_num = apng_frame_number;
- info->apng_first_frame_is_cover = first_frame_is_cover;
- info->apng_shared_insert_index = first_IDAT_index;
- info->apng_frames = calloc(apng_frame_number, sizeof(yy_png_frame_info));
- if (!info->apng_frames) {
- yy_png_info_release(info);
- return NULL;
- }
- info->apng_shared_chunk_indexs = calloc(info->chunk_num, sizeof(uint32_t));
- if (!info->apng_shared_chunk_indexs) {
- yy_png_info_release(info);
- return NULL;
- }
-
- int32_t frame_index = -1;
- uint32_t *shared_chunk_index = info->apng_shared_chunk_indexs;
- for (int32_t i = 0; i < info->chunk_num; i++) {
- yy_png_chunk_info *chunk = info->chunks + i;
- switch (chunk->fourcc) {
- case YY_FOUR_CC('I', 'D', 'A', 'T'): {
- if (info->apng_shared_insert_index == 0) {
- info->apng_shared_insert_index = i;
- }
- if (first_frame_is_cover) {
- yy_png_frame_info *frame = info->apng_frames + frame_index;
- frame->chunk_num++;
- frame->chunk_size += chunk->length + 12;
- }
- } break;
- case YY_FOUR_CC('a', 'c', 'T', 'L'): {
- } break;
- case YY_FOUR_CC('f', 'c', 'T', 'L'): {
- frame_index++;
- yy_png_frame_info *frame = info->apng_frames + frame_index;
- frame->chunk_index = i + 1;
- yy_png_chunk_fcTL_read(&frame->frame_control, data + chunk->offset + 8);
- } break;
- case YY_FOUR_CC('f', 'd', 'A', 'T'): {
- yy_png_frame_info *frame = info->apng_frames + frame_index;
- frame->chunk_num++;
- frame->chunk_size += chunk->length + 12;
- } break;
- default: {
- *shared_chunk_index = i;
- shared_chunk_index++;
- info->apng_shared_chunk_size += chunk->length + 12;
- info->apng_shared_chunk_num++;
- } break;
- }
- }
- }
- return info;
- }
- /**
- Copy a png frame data from an apng file.
-
- @param data apng file data
- @param info png info
- @param index frame index (zero-based)
- @param size output, the size of the frame data
- @return A frame data (single-frame png file), call free() to release the data.
- Returns NULL if an error occurs.
- */
- static uint8_t *yy_png_copy_frame_data_at_index(const uint8_t *data,
- const yy_png_info *info,
- const uint32_t index,
- uint32_t *size) {
- if (index >= info->apng_frame_num) return NULL;
-
- yy_png_frame_info *frame_info = info->apng_frames + index;
- uint32_t frame_remux_size = 8 /* PNG Header */ + info->apng_shared_chunk_size + frame_info->chunk_size;
- if (!(info->apng_first_frame_is_cover && index == 0)) {
- frame_remux_size -= frame_info->chunk_num * 4; // remove fdAT sequence number
- }
- uint8_t *frame_data = malloc(frame_remux_size);
- if (!frame_data) return NULL;
- *size = frame_remux_size;
-
- uint32_t data_offset = 0;
- bool inserted = false;
- memcpy(frame_data, data, 8); // PNG File Header
- data_offset += 8;
- for (uint32_t i = 0; i < info->apng_shared_chunk_num; i++) {
- uint32_t shared_chunk_index = info->apng_shared_chunk_indexs[i];
- yy_png_chunk_info *shared_chunk_info = info->chunks + shared_chunk_index;
-
- if (shared_chunk_index >= info->apng_shared_insert_index && !inserted) { // replace IDAT with fdAT
- inserted = true;
- for (uint32_t c = 0; c < frame_info->chunk_num; c++) {
- yy_png_chunk_info *insert_chunk_info = info->chunks + frame_info->chunk_index + c;
- if (insert_chunk_info->fourcc == YY_FOUR_CC('f', 'd', 'A', 'T')) {
- *((uint32_t *)(frame_data + data_offset)) = yy_swap_endian_uint32(insert_chunk_info->length - 4);
- *((uint32_t *)(frame_data + data_offset + 4)) = YY_FOUR_CC('I', 'D', 'A', 'T');
- memcpy(frame_data + data_offset + 8, data + insert_chunk_info->offset + 12, insert_chunk_info->length - 4);
- uint32_t crc = (uint32_t)crc32(0, frame_data + data_offset + 4, insert_chunk_info->length);
- *((uint32_t *)(frame_data + data_offset + insert_chunk_info->length + 4)) = yy_swap_endian_uint32(crc);
- data_offset += insert_chunk_info->length + 8;
- } else { // IDAT
- memcpy(frame_data + data_offset, data + insert_chunk_info->offset, insert_chunk_info->length + 12);
- data_offset += insert_chunk_info->length + 12;
- }
- }
- }
-
- if (shared_chunk_info->fourcc == YY_FOUR_CC('I', 'H', 'D', 'R')) {
- uint8_t tmp[25] = {0};
- memcpy(tmp, data + shared_chunk_info->offset, 25);
- yy_png_chunk_IHDR IHDR = info->header;
- IHDR.width = frame_info->frame_control.width;
- IHDR.height = frame_info->frame_control.height;
- yy_png_chunk_IHDR_write(&IHDR, tmp + 8);
- *((uint32_t *)(tmp + 21)) = yy_swap_endian_uint32((uint32_t)crc32(0, tmp + 4, 17));
- memcpy(frame_data + data_offset, tmp, 25);
- data_offset += 25;
- } else {
- memcpy(frame_data + data_offset, data + shared_chunk_info->offset, shared_chunk_info->length + 12);
- data_offset += shared_chunk_info->length + 12;
- }
- }
- return frame_data;
- }
- ////////////////////////////////////////////////////////////////////////////////
- #pragma mark - Helper
- /// Returns byte-aligned size.
- static inline size_t YYImageByteAlign(size_t size, size_t alignment) {
- return ((size + (alignment - 1)) / alignment) * alignment;
- }
- /// Convert degree to radians
- static inline CGFloat YYImageDegreesToRadians(CGFloat degrees) {
- return degrees * M_PI / 180;
- }
- CGColorSpaceRef YYCGColorSpaceGetDeviceRGB() {
- static CGColorSpaceRef space;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- space = CGColorSpaceCreateDeviceRGB();
- });
- return space;
- }
- CGColorSpaceRef YYCGColorSpaceGetDeviceGray() {
- static CGColorSpaceRef space;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- space = CGColorSpaceCreateDeviceGray();
- });
- return space;
- }
- BOOL YYCGColorSpaceIsDeviceRGB(CGColorSpaceRef space) {
- return space && CFEqual(space, YYCGColorSpaceGetDeviceRGB());
- }
- BOOL YYCGColorSpaceIsDeviceGray(CGColorSpaceRef space) {
- return space && CFEqual(space, YYCGColorSpaceGetDeviceGray());
- }
- /**
- A callback used in CGDataProviderCreateWithData() to release data.
-
- Example:
-
- void *data = malloc(size);
- CGDataProviderRef provider = CGDataProviderCreateWithData(data, data, size, YYCGDataProviderReleaseDataCallback);
- */
- static void YYCGDataProviderReleaseDataCallback(void *info, const void *data, size_t size) {
- if (info) free(info);
- }
- /**
- Decode an image to bitmap buffer with the specified format.
-
- @param srcImage Source image.
- @param dest Destination buffer. It should be zero before call this method.
- If decode succeed, you should release the dest->data using free().
- @param destFormat Destination bitmap format.
-
- @return Whether succeed.
-
- @warning This method support iOS7.0 and later. If call it on iOS6, it just returns NO.
- CG_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0)
- */
- static BOOL YYCGImageDecodeToBitmapBufferWithAnyFormat(CGImageRef srcImage, vImage_Buffer *dest, vImage_CGImageFormat *destFormat) {
- if (!srcImage || (((long)vImageConvert_AnyToAny) + 1 == 1) || !destFormat || !dest) return NO;
- size_t width = CGImageGetWidth(srcImage);
- size_t height = CGImageGetHeight(srcImage);
- if (width == 0 || height == 0) return NO;
- dest->data = NULL;
-
- vImage_Error error = kvImageNoError;
- CFDataRef srcData = NULL;
- vImageConverterRef convertor = NULL;
- vImage_CGImageFormat srcFormat = {0};
- srcFormat.bitsPerComponent = (uint32_t)CGImageGetBitsPerComponent(srcImage);
- srcFormat.bitsPerPixel = (uint32_t)CGImageGetBitsPerPixel(srcImage);
- srcFormat.colorSpace = CGImageGetColorSpace(srcImage);
- srcFormat.bitmapInfo = CGImageGetBitmapInfo(srcImage) | CGImageGetAlphaInfo(srcImage);
-
- convertor = vImageConverter_CreateWithCGImageFormat(&srcFormat, destFormat, NULL, kvImageNoFlags, NULL);
- if (!convertor) goto fail;
-
- CGDataProviderRef srcProvider = CGImageGetDataProvider(srcImage);
- srcData = srcProvider ? CGDataProviderCopyData(srcProvider) : NULL; // decode
- size_t srcLength = srcData ? CFDataGetLength(srcData) : 0;
- const void *srcBytes = srcData ? CFDataGetBytePtr(srcData) : NULL;
- if (srcLength == 0 || !srcBytes) goto fail;
-
- vImage_Buffer src = {0};
- src.data = (void *)srcBytes;
- src.width = width;
- src.height = height;
- src.rowBytes = CGImageGetBytesPerRow(srcImage);
-
- error = vImageBuffer_Init(dest, height, width, 32, kvImageNoFlags);
- if (error != kvImageNoError) goto fail;
-
- error = vImageConvert_AnyToAny(convertor, &src, dest, NULL, kvImageNoFlags); // convert
- if (error != kvImageNoError) goto fail;
-
- CFRelease(convertor);
- CFRelease(srcData);
- return YES;
-
- fail:
- if (convertor) CFRelease(convertor);
- if (srcData) CFRelease(srcData);
- if (dest->data) free(dest->data);
- dest->data = NULL;
- return NO;
- }
- /**
- Decode an image to bitmap buffer with the 32bit format (such as ARGB8888).
-
- @param srcImage Source image.
- @param dest Destination buffer. It should be zero before call this method.
- If decode succeed, you should release the dest->data using free().
- @param bitmapInfo Destination bitmap format.
-
- @return Whether succeed.
- */
- static BOOL YYCGImageDecodeToBitmapBufferWith32BitFormat(CGImageRef srcImage, vImage_Buffer *dest, CGBitmapInfo bitmapInfo) {
- if (!srcImage || !dest) return NO;
- size_t width = CGImageGetWidth(srcImage);
- size_t height = CGImageGetHeight(srcImage);
- if (width == 0 || height == 0) return NO;
-
- BOOL hasAlpha = NO;
- BOOL alphaFirst = NO;
- BOOL alphaPremultiplied = NO;
- BOOL byteOrderNormal = NO;
-
- switch (bitmapInfo & kCGBitmapAlphaInfoMask) {
- case kCGImageAlphaPremultipliedLast: {
- hasAlpha = YES;
- alphaPremultiplied = YES;
- } break;
- case kCGImageAlphaPremultipliedFirst: {
- hasAlpha = YES;
- alphaPremultiplied = YES;
- alphaFirst = YES;
- } break;
- case kCGImageAlphaLast: {
- hasAlpha = YES;
- } break;
- case kCGImageAlphaFirst: {
- hasAlpha = YES;
- alphaFirst = YES;
- } break;
- case kCGImageAlphaNoneSkipLast: {
- } break;
- case kCGImageAlphaNoneSkipFirst: {
- alphaFirst = YES;
- } break;
- default: {
- return NO;
- } break;
- }
-
- switch (bitmapInfo & kCGBitmapByteOrderMask) {
- case kCGBitmapByteOrderDefault: {
- byteOrderNormal = YES;
- } break;
- case kCGBitmapByteOrder32Little: {
- } break;
- case kCGBitmapByteOrder32Big: {
- byteOrderNormal = YES;
- } break;
- default: {
- return NO;
- } break;
- }
-
- /*
- Try convert with vImageConvert_AnyToAny() (avaliable since iOS 7.0).
- If fail, try decode with CGContextDrawImage().
- CGBitmapContext use a premultiplied alpha format, unpremultiply may lose precision.
- */
- vImage_CGImageFormat destFormat = {0};
- destFormat.bitsPerComponent = 8;
- destFormat.bitsPerPixel = 32;
- destFormat.colorSpace = YYCGColorSpaceGetDeviceRGB();
- destFormat.bitmapInfo = bitmapInfo;
- dest->data = NULL;
- if (YYCGImageDecodeToBitmapBufferWithAnyFormat(srcImage, dest, &destFormat)) return YES;
-
- CGBitmapInfo contextBitmapInfo = bitmapInfo & kCGBitmapByteOrderMask;
- if (!hasAlpha || alphaPremultiplied) {
- contextBitmapInfo |= (bitmapInfo & kCGBitmapAlphaInfoMask);
- } else {
- contextBitmapInfo |= alphaFirst ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaPremultipliedLast;
- }
- CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, YYCGColorSpaceGetDeviceRGB(), contextBitmapInfo);
- if (!context) goto fail;
-
- CGContextDrawImage(context, CGRectMake(0, 0, width, height), srcImage); // decode and convert
- size_t bytesPerRow = CGBitmapContextGetBytesPerRow(context);
- size_t length = height * bytesPerRow;
- void *data = CGBitmapContextGetData(context);
- if (length == 0 || !data) goto fail;
-
- dest->data = malloc(length);
- dest->width = width;
- dest->height = height;
- dest->rowBytes = bytesPerRow;
- if (!dest->data) goto fail;
-
- if (hasAlpha && !alphaPremultiplied) {
- vImage_Buffer tmpSrc = {0};
- tmpSrc.data = data;
- tmpSrc.width = width;
- tmpSrc.height = height;
- tmpSrc.rowBytes = bytesPerRow;
- vImage_Error error;
- if (alphaFirst && byteOrderNormal) {
- error = vImageUnpremultiplyData_ARGB8888(&tmpSrc, dest, kvImageNoFlags);
- } else {
- error = vImageUnpremultiplyData_RGBA8888(&tmpSrc, dest, kvImageNoFlags);
- }
- if (error != kvImageNoError) goto fail;
- } else {
- memcpy(dest->data, data, length);
- }
-
- CFRelease(context);
- return YES;
-
- fail:
- if (context) CFRelease(context);
- if (dest->data) free(dest->data);
- dest->data = NULL;
- return NO;
- return NO;
- }
- CGImageRef YYCGImageCreateDecodedCopy(CGImageRef imageRef, BOOL decodeForDisplay) {
- if (!imageRef) return NULL;
- size_t width = CGImageGetWidth(imageRef);
- size_t height = CGImageGetHeight(imageRef);
- if (width == 0 || height == 0) return NULL;
-
- if (decodeForDisplay) { //decode with redraw (may lose some precision)
- CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(imageRef) & kCGBitmapAlphaInfoMask;
- BOOL hasAlpha = NO;
- if (alphaInfo == kCGImageAlphaPremultipliedLast ||
- alphaInfo == kCGImageAlphaPremultipliedFirst ||
- alphaInfo == kCGImageAlphaLast ||
- alphaInfo == kCGImageAlphaFirst) {
- hasAlpha = YES;
- }
- // BGRA8888 (premultiplied) or BGRX8888
- // same as UIGraphicsBeginImageContext() and -[UIView drawRect:]
- CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
- bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
- CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, YYCGColorSpaceGetDeviceRGB(), bitmapInfo);
- if (!context) return NULL;
- CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef); // decode
- CGImageRef newImage = CGBitmapContextCreateImage(context);
- CFRelease(context);
- return newImage;
-
- } else {
- CGColorSpaceRef space = CGImageGetColorSpace(imageRef);
- size_t bitsPerComponent = CGImageGetBitsPerComponent(imageRef);
- size_t bitsPerPixel = CGImageGetBitsPerPixel(imageRef);
- size_t bytesPerRow = CGImageGetBytesPerRow(imageRef);
- CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
- if (bytesPerRow == 0 || width == 0 || height == 0) return NULL;
-
- CGDataProviderRef dataProvider = CGImageGetDataProvider(imageRef);
- if (!dataProvider) return NULL;
- CFDataRef data = CGDataProviderCopyData(dataProvider); // decode
- if (!data) return NULL;
-
- CGDataProviderRef newProvider = CGDataProviderCreateWithCFData(data);
- CFRelease(data);
- if (!newProvider) return NULL;
-
- CGImageRef newImage = CGImageCreate(width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, space, bitmapInfo, newProvider, NULL, false, kCGRenderingIntentDefault);
- CFRelease(newProvider);
- return newImage;
- }
- }
- CGImageRef YYCGImageCreateAffineTransformCopy(CGImageRef imageRef, CGAffineTransform transform, CGSize destSize, CGBitmapInfo destBitmapInfo) {
- if (!imageRef) return NULL;
- size_t srcWidth = CGImageGetWidth(imageRef);
- size_t srcHeight = CGImageGetHeight(imageRef);
- size_t destWidth = round(destSize.width);
- size_t destHeight = round(destSize.height);
- if (srcWidth == 0 || srcHeight == 0 || destWidth == 0 || destHeight == 0) return NULL;
-
- CGDataProviderRef tmpProvider = NULL, destProvider = NULL;
- CGImageRef tmpImage = NULL, destImage = NULL;
- vImage_Buffer src = {0}, tmp = {0}, dest = {0};
- if(!YYCGImageDecodeToBitmapBufferWith32BitFormat(imageRef, &src, kCGImageAlphaFirst | kCGBitmapByteOrderDefault)) return NULL;
-
- size_t destBytesPerRow = YYImageByteAlign(destWidth * 4, 32);
- tmp.data = malloc(destHeight * destBytesPerRow);
- if (!tmp.data) goto fail;
-
- tmp.width = destWidth;
- tmp.height = destHeight;
- tmp.rowBytes = destBytesPerRow;
- vImage_CGAffineTransform vTransform = *((vImage_CGAffineTransform *)&transform);
- uint8_t backColor[4] = {0};
- vImage_Error error = vImageAffineWarpCG_ARGB8888(&src, &tmp, NULL, &vTransform, backColor, kvImageBackgroundColorFill);
- if (error != kvImageNoError) goto fail;
- free(src.data);
- src.data = NULL;
-
- tmpProvider = CGDataProviderCreateWithData(tmp.data, tmp.data, destHeight * destBytesPerRow, YYCGDataProviderReleaseDataCallback);
- if (!tmpProvider) goto fail;
- tmp.data = NULL; // hold by provider
- tmpImage = CGImageCreate(destWidth, destHeight, 8, 32, destBytesPerRow, YYCGColorSpaceGetDeviceRGB(), kCGImageAlphaFirst | kCGBitmapByteOrderDefault, tmpProvider, NULL, false, kCGRenderingIntentDefault);
- if (!tmpImage) goto fail;
- CFRelease(tmpProvider);
- tmpProvider = NULL;
-
- if ((destBitmapInfo & kCGBitmapAlphaInfoMask) == kCGImageAlphaFirst &&
- (destBitmapInfo & kCGBitmapByteOrderMask) != kCGBitmapByteOrder32Little) {
- return tmpImage;
- }
-
- if (!YYCGImageDecodeToBitmapBufferWith32BitFormat(tmpImage, &dest, destBitmapInfo)) goto fail;
- CFRelease(tmpImage);
- tmpImage = NULL;
-
- destProvider = CGDataProviderCreateWithData(dest.data, dest.data, destHeight * destBytesPerRow, YYCGDataProviderReleaseDataCallback);
- if (!destProvider) goto fail;
- dest.data = NULL; // hold by provider
- destImage = CGImageCreate(destWidth, destHeight, 8, 32, destBytesPerRow, YYCGColorSpaceGetDeviceRGB(), destBitmapInfo, destProvider, NULL, false, kCGRenderingIntentDefault);
- if (!destImage) goto fail;
- CFRelease(destProvider);
- destProvider = NULL;
-
- return destImage;
-
- fail:
- if (src.data) free(src.data);
- if (tmp.data) free(tmp.data);
- if (dest.data) free(dest.data);
- if (tmpProvider) CFRelease(tmpProvider);
- if (tmpImage) CFRelease(tmpImage);
- if (destProvider) CFRelease(destProvider);
- return NULL;
- }
- UIImageOrientation YYUIImageOrientationFromEXIFValue(NSInteger value) {
- switch (value) {
- case kCGImagePropertyOrientationUp: return UIImageOrientationUp;
- case kCGImagePropertyOrientationDown: return UIImageOrientationDown;
- case kCGImagePropertyOrientationLeft: return UIImageOrientationLeft;
- case kCGImagePropertyOrientationRight: return UIImageOrientationRight;
- case kCGImagePropertyOrientationUpMirrored: return UIImageOrientationUpMirrored;
- case kCGImagePropertyOrientationDownMirrored: return UIImageOrientationDownMirrored;
- case kCGImagePropertyOrientationLeftMirrored: return UIImageOrientationLeftMirrored;
- case kCGImagePropertyOrientationRightMirrored: return UIImageOrientationRightMirrored;
- default: return UIImageOrientationUp;
- }
- }
- NSInteger YYUIImageOrientationToEXIFValue(UIImageOrientation orientation) {
- switch (orientation) {
- case UIImageOrientationUp: return kCGImagePropertyOrientationUp;
- case UIImageOrientationDown: return kCGImagePropertyOrientationDown;
- case UIImageOrientationLeft: return kCGImagePropertyOrientationLeft;
- case UIImageOrientationRight: return kCGImagePropertyOrientationRight;
- case UIImageOrientationUpMirrored: return kCGImagePropertyOrientationUpMirrored;
- case UIImageOrientationDownMirrored: return kCGImagePropertyOrientationDownMirrored;
- case UIImageOrientationLeftMirrored: return kCGImagePropertyOrientationLeftMirrored;
- case UIImageOrientationRightMirrored: return kCGImagePropertyOrientationRightMirrored;
- default: return kCGImagePropertyOrientationUp;
- }
- }
- CGImageRef YYCGImageCreateCopyWithOrientation(CGImageRef imageRef, UIImageOrientation orientation, CGBitmapInfo destBitmapInfo) {
- if (!imageRef) return NULL;
- if (orientation == UIImageOrientationUp) return (CGImageRef)CFRetain(imageRef);
-
- size_t width = CGImageGetWidth(imageRef);
- size_t height = CGImageGetHeight(imageRef);
-
- CGAffineTransform transform = CGAffineTransformIdentity;
- BOOL swapWidthAndHeight = NO;
- switch (orientation) {
- case UIImageOrientationDown: {
- transform = CGAffineTransformMakeRotation(YYImageDegreesToRadians(180));
- transform = CGAffineTransformTranslate(transform, -(CGFloat)width, -(CGFloat)height);
- } break;
- case UIImageOrientationLeft: {
- transform = CGAffineTransformMakeRotation(YYImageDegreesToRadians(90));
- transform = CGAffineTransformTranslate(transform, -(CGFloat)0, -(CGFloat)height);
- swapWidthAndHeight = YES;
- } break;
- case UIImageOrientationRight: {
- transform = CGAffineTransformMakeRotation(YYImageDegreesToRadians(-90));
- transform = CGAffineTransformTranslate(transform, -(CGFloat)width, (CGFloat)0);
- swapWidthAndHeight = YES;
- } break;
- case UIImageOrientationUpMirrored: {
- transform = CGAffineTransformTranslate(transform, (CGFloat)width, 0);
- transform = CGAffineTransformScale(transform, -1, 1);
- } break;
- case UIImageOrientationDownMirrored: {
- transform = CGAffineTransformTranslate(transform, 0, (CGFloat)height);
- transform = CGAffineTransformScale(transform, 1, -1);
- } break;
- case UIImageOrientationLeftMirrored: {
- transform = CGAffineTransformMakeRotation(YYImageDegreesToRadians(-90));
- transform = CGAffineTransformScale(transform, 1, -1);
- transform = CGAffineTransformTranslate(transform, -(CGFloat)width, -(CGFloat)height);
- swapWidthAndHeight = YES;
- } break;
- case UIImageOrientationRightMirrored: {
- transform = CGAffineTransformMakeRotation(YYImageDegreesToRadians(90));
- transform = CGAffineTransformScale(transform, 1, -1);
- swapWidthAndHeight = YES;
- } break;
- default: break;
- }
- if (CGAffineTransformIsIdentity(transform)) return (CGImageRef)CFRetain(imageRef);
-
- CGSize destSize = {width, height};
- if (swapWidthAndHeight) {
- destSize.width = height;
- destSize.height = width;
- }
-
- return YYCGImageCreateAffineTransformCopy(imageRef, transform, destSize, destBitmapInfo);
- }
- YYImageType YYImageDetectType(CFDataRef data) {
- if (!data) return YYImageTypeUnknown;
- uint64_t length = CFDataGetLength(data);
- if (length < 16) return YYImageTypeUnknown;
-
- const char *bytes = (char *)CFDataGetBytePtr(data);
-
- uint32_t magic4 = *((uint32_t *)bytes);
- switch (magic4) {
- case YY_FOUR_CC(0x4D, 0x4D, 0x00, 0x2A): { // big endian TIFF
- return YYImageTypeTIFF;
- } break;
-
- case YY_FOUR_CC(0x49, 0x49, 0x2A, 0x00): { // little endian TIFF
- return YYImageTypeTIFF;
- } break;
-
- case YY_FOUR_CC(0x00, 0x00, 0x01, 0x00): { // ICO
- return YYImageTypeICO;
- } break;
-
- case YY_FOUR_CC(0x00, 0x00, 0x02, 0x00): { // CUR
- return YYImageTypeICO;
- } break;
-
- case YY_FOUR_CC('i', 'c', 'n', 's'): { // ICNS
- return YYImageTypeICNS;
- } break;
-
- case YY_FOUR_CC('G', 'I', 'F', '8'): { // GIF
- return YYImageTypeGIF;
- } break;
-
- case YY_FOUR_CC(0x89, 'P', 'N', 'G'): { // PNG
- uint32_t tmp = *((uint32_t *)(bytes + 4));
- if (tmp == YY_FOUR_CC('\r', '\n', 0x1A, '\n')) {
- return YYImageTypePNG;
- }
- } break;
-
- case YY_FOUR_CC('R', 'I', 'F', 'F'): { // WebP
- uint32_t tmp = *((uint32_t *)(bytes + 8));
- if (tmp == YY_FOUR_CC('W', 'E', 'B', 'P')) {
- return YYImageTypeWebP;
- }
- } break;
- /*
- case YY_FOUR_CC('B', 'P', 'G', 0xFB): { // BPG
- return YYImageTypeBPG;
- } break;
- */
- }
-
- uint16_t magic2 = *((uint16_t *)bytes);
- switch (magic2) {
- case YY_TWO_CC('B', 'A'):
- case YY_TWO_CC('B', 'M'):
- case YY_TWO_CC('I', 'C'):
- case YY_TWO_CC('P', 'I'):
- case YY_TWO_CC('C', 'I'):
- case YY_TWO_CC('C', 'P'): { // BMP
- return YYImageTypeBMP;
- }
- case YY_TWO_CC(0xFF, 0x4F): { // JPEG2000
- return YYImageTypeJPEG2000;
- }
- }
-
- // JPG FF D8 FF
- if (memcmp(bytes,"\377\330\377",3) == 0) return YYImageTypeJPEG;
-
- // JP2
- if (memcmp(bytes + 4, "\152\120\040\040\015", 5) == 0) return YYImageTypeJPEG2000;
-
- return YYImageTypeUnknown;
- }
- CFStringRef YYImageTypeToUTType(YYImageType type) {
- switch (type) {
- case YYImageTypeJPEG: return kUTTypeJPEG;
- case YYImageTypeJPEG2000: return kUTTypeJPEG2000;
- case YYImageTypeTIFF: return kUTTypeTIFF;
- case YYImageTypeBMP: return kUTTypeBMP;
- case YYImageTypeICO: return kUTTypeICO;
- case YYImageTypeICNS: return kUTTypeAppleICNS;
- case YYImageTypeGIF: return kUTTypeGIF;
- case YYImageTypePNG: return kUTTypePNG;
- default: return NULL;
- }
- }
- YYImageType YYImageTypeFromUTType(CFStringRef uti) {
- static NSDictionary *dic;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- dic = @{(id)kUTTypeJPEG : @(YYImageTypeJPEG),
- (id)kUTTypeJPEG2000 : @(YYImageTypeJPEG2000),
- (id)kUTTypeTIFF : @(YYImageTypeTIFF),
- (id)kUTTypeBMP : @(YYImageTypeBMP),
- (id)kUTTypeICO : @(YYImageTypeICO),
- (id)kUTTypeAppleICNS : @(YYImageTypeICNS),
- (id)kUTTypeGIF : @(YYImageTypeGIF),
- (id)kUTTypePNG : @(YYImageTypePNG)};
- });
- if (!uti) return YYImageTypeUnknown;
- NSNumber *num = dic[(__bridge __strong id)(uti)];
- return num.unsignedIntegerValue;
- }
- NSString *YYImageTypeGetExtension(YYImageType type) {
- switch (type) {
- case YYImageTypeJPEG: return @"jpg";
- case YYImageTypeJPEG2000: return @"jp2";
- case YYImageTypeTIFF: return @"tiff";
- case YYImageTypeBMP: return @"bmp";
- case YYImageTypeICO: return @"ico";
- case YYImageTypeICNS: return @"icns";
- case YYImageTypeGIF: return @"gif";
- case YYImageTypePNG: return @"png";
- case YYImageTypeWebP: return @"webp";
- default: return nil;
- }
- }
- CFDataRef YYCGImageCreateEncodedData(CGImageRef imageRef, YYImageType type, CGFloat quality) {
- if (!imageRef) return nil;
- quality = quality < 0 ? 0 : quality > 1 ? 1 : quality;
-
- if (type == YYImageTypeWebP) {
- #if YYIMAGE_WEBP_ENABLED
- if (quality == 1) {
- return YYCGImageCreateEncodedWebPData(imageRef, YES, quality, 4, YYImagePresetDefault);
- } else {
- return YYCGImageCreateEncodedWebPData(imageRef, NO, quality, 4, YYImagePresetDefault);
- }
- #else
- return NULL;
- #endif
- }
-
- CFStringRef uti = YYImageTypeToUTType(type);
- if (!uti) return nil;
-
- CFMutableDataRef data = CFDataCreateMutable(CFAllocatorGetDefault(), 0);
- if (!data) return NULL;
- CGImageDestinationRef dest = CGImageDestinationCreateWithData(data, uti, 1, NULL);
- if (!dest) {
- CFRelease(data);
- return NULL;
- }
- NSDictionary *options = @{(id)kCGImageDestinationLossyCompressionQuality : @(quality) };
- CGImageDestinationAddImage(dest, imageRef, (CFDictionaryRef)options);
- if (!CGImageDestinationFinalize(dest)) {
- CFRelease(data);
- CFRelease(dest);
- return nil;
- }
- CFRelease(dest);
-
- if (CFDataGetLength(data) == 0) {
- CFRelease(data);
- return NULL;
- }
- return data;
- }
- #if YYIMAGE_WEBP_ENABLED
- BOOL YYImageWebPAvailable() {
- return YES;
- }
- CFDataRef YYCGImageCreateEncodedWebPData(CGImageRef imageRef, BOOL lossless, CGFloat quality, int compressLevel, YYImagePreset preset) {
- if (!imageRef) return nil;
- size_t width = CGImageGetWidth(imageRef);
- size_t height = CGImageGetHeight(imageRef);
- if (width == 0 || width > WEBP_MAX_DIMENSION) return nil;
- if (height == 0 || height > WEBP_MAX_DIMENSION) return nil;
-
- vImage_Buffer buffer = {0};
- if(!YYCGImageDecodeToBitmapBufferWith32BitFormat(imageRef, &buffer, kCGImageAlphaLast | kCGBitmapByteOrderDefault)) return nil;
-
- WebPConfig config = {0};
- WebPPicture picture = {0};
- WebPMemoryWriter writer = {0};
- CFDataRef webpData = NULL;
- BOOL pictureNeedFree = NO;
-
- quality = quality < 0 ? 0 : quality > 1 ? 1 : quality;
- preset = preset > YYImagePresetText ? YYImagePresetDefault : preset;
- compressLevel = compressLevel < 0 ? 0 : compressLevel > 6 ? 6 : compressLevel;
- if (!WebPConfigPreset(&config, (WebPPreset)preset, quality)) goto fail;
-
- config.quality = round(quality * 100.0);
- config.lossless = lossless;
- config.method = compressLevel;
- switch ((WebPPreset)preset) {
- case WEBP_PRESET_DEFAULT: {
- config.image_hint = WEBP_HINT_DEFAULT;
- } break;
- case WEBP_PRESET_PICTURE: {
- config.image_hint = WEBP_HINT_PICTURE;
- } break;
- case WEBP_PRESET_PHOTO: {
- config.image_hint = WEBP_HINT_PHOTO;
- } break;
- case WEBP_PRESET_DRAWING:
- case WEBP_PRESET_ICON:
- case WEBP_PRESET_TEXT: {
- config.image_hint = WEBP_HINT_GRAPH;
- } break;
- }
- if (!WebPValidateConfig(&config)) goto fail;
-
- if (!WebPPictureInit(&picture)) goto fail;
- pictureNeedFree = YES;
- picture.width = (int)buffer.width;
- picture.height = (int)buffer.height;
- picture.use_argb = lossless;
- if(!WebPPictureImportRGBA(&picture, buffer.data, (int)buffer.rowBytes)) goto fail;
-
- WebPMemoryWriterInit(&writer);
- picture.writer = WebPMemoryWrite;
- picture.custom_ptr = &writer;
- if(!WebPEncode(&config, &picture)) goto fail;
-
- webpData = CFDataCreate(CFAllocatorGetDefault(), writer.mem, writer.size);
- free(writer.mem);
- WebPPictureFree(&picture);
- free(buffer.data);
- return webpData;
-
- fail:
- if (buffer.data) free(buffer.data);
- if (pictureNeedFree) WebPPictureFree(&picture);
- return nil;
- }
- NSUInteger YYImageGetWebPFrameCount(CFDataRef webpData) {
- if (!webpData || CFDataGetLength(webpData) == 0) return 0;
-
- WebPData data = {CFDataGetBytePtr(webpData), CFDataGetLength(webpData)};
- WebPDemuxer *demuxer = WebPDemux(&data);
- if (!demuxer) return 0;
- NSUInteger webpFrameCount = WebPDemuxGetI(demuxer, WEBP_FF_FRAME_COUNT);
- WebPDemuxDelete(demuxer);
- return webpFrameCount;
- }
- CGImageRef YYCGImageCreateWithWebPData(CFDataRef webpData,
- BOOL decodeForDisplay,
- BOOL useThreads,
- BOOL bypassFiltering,
- BOOL noFancyUpsampling) {
- /*
- Call WebPDecode() on a multi-frame webp data will get an error (VP8_STATUS_UNSUPPORTED_FEATURE).
- Use WebPDemuxer to unpack it first.
- */
- WebPData data = {0};
- WebPDemuxer *demuxer = NULL;
-
- int frameCount = 0, canvasWidth = 0, canvasHeight = 0;
- WebPIterator iter = {0};
- BOOL iterInited = NO;
- const uint8_t *payload = NULL;
- size_t payloadSize = 0;
- WebPDecoderConfig config = {0};
-
- BOOL hasAlpha = NO;
- size_t bitsPerComponent = 0, bitsPerPixel = 0, bytesPerRow = 0, destLength = 0;
- CGBitmapInfo bitmapInfo = 0;
- WEBP_CSP_MODE colorspace = 0;
- void *destBytes = NULL;
- CGDataProviderRef provider = NULL;
- CGImageRef imageRef = NULL;
-
- if (!webpData || CFDataGetLength(webpData) == 0) return NULL;
- data.bytes = CFDataGetBytePtr(webpData);
- data.size = CFDataGetLength(webpData);
- demuxer = WebPDemux(&data);
- if (!demuxer) goto fail;
-
- frameCount = WebPDemuxGetI(demuxer, WEBP_FF_FRAME_COUNT);
- if (frameCount == 0) {
- goto fail;
-
- } else if (frameCount == 1) { // single-frame
- payload = data.bytes;
- payloadSize = data.size;
- if (!WebPInitDecoderConfig(&config)) goto fail;
- if (WebPGetFeatures(payload , payloadSize, &config.input) != VP8_STATUS_OK) goto fail;
- canvasWidth = config.input.width;
- canvasHeight = config.input.height;
-
- } else { // multi-frame
- canvasWidth = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_WIDTH);
- canvasHeight = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_HEIGHT);
- if (canvasWidth < 1 || canvasHeight < 1) goto fail;
-
- if (!WebPDemuxGetFrame(demuxer, 1, &iter)) goto fail;
- iterInited = YES;
-
- if (iter.width > canvasWidth || iter.height > canvasHeight) goto fail;
- payload = iter.fragment.bytes;
- payloadSize = iter.fragment.size;
-
- if (!WebPInitDecoderConfig(&config)) goto fail;
- if (WebPGetFeatures(payload , payloadSize, &config.input) != VP8_STATUS_OK) goto fail;
- }
- if (payload == NULL || payloadSize == 0) goto fail;
-
- hasAlpha = config.input.has_alpha;
- bitsPerComponent = 8;
- bitsPerPixel = 32;
- bytesPerRow = YYImageByteAlign(bitsPerPixel / 8 * canvasWidth, 32);
- destLength = bytesPerRow * canvasHeight;
- if (decodeForDisplay) {
- bitmapInfo = kCGBitmapByteOrder32Host;
- bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
- colorspace = MODE_bgrA; // small endian
- } else {
- bitmapInfo = kCGBitmapByteOrderDefault;
- bitmapInfo |= hasAlpha ? kCGImageAlphaLast : kCGImageAlphaNoneSkipLast;
- colorspace = MODE_RGBA;
- }
- destBytes = calloc(1, destLength);
- if (!destBytes) goto fail;
-
- config.options.use_threads = useThreads; //speed up 23%
- config.options.bypass_filtering = bypassFiltering; //speed up 11%, cause some banding
- config.options.no_fancy_upsampling = noFancyUpsampling; //speed down 16%, lose some details
- config.output.colorspace = colorspace;
- config.output.is_external_memory = 1;
- config.output.u.RGBA.rgba = destBytes;
- config.output.u.RGBA.stride = (int)bytesPerRow;
- config.output.u.RGBA.size = destLength;
-
- VP8StatusCode result = WebPDecode(payload, payloadSize, &config);
- if ((result != VP8_STATUS_OK) && (result != VP8_STATUS_NOT_ENOUGH_DATA)) goto fail;
-
- if (iter.x_offset != 0 || iter.y_offset != 0) {
- void *tmp = calloc(1, destLength);
- if (tmp) {
- vImage_Buffer src = {destBytes, canvasHeight, canvasWidth, bytesPerRow};
- vImage_Buffer dest = {tmp, canvasHeight, canvasWidth, bytesPerRow};
- vImage_CGAffineTransform transform = {1, 0, 0, 1, iter.x_offset, -iter.y_offset};
- uint8_t backColor[4] = {0};
- vImageAffineWarpCG_ARGB8888(&src, &dest, NULL, &transform, backColor, kvImageBackgroundColorFill);
- memcpy(destBytes, tmp, destLength);
- free(tmp);
- }
- }
-
- provider = CGDataProviderCreateWithData(destBytes, destBytes, destLength, YYCGDataProviderReleaseDataCallback);
- if (!provider) goto fail;
- destBytes = NULL; // hold by provider
-
- imageRef = CGImageCreate(canvasWidth, canvasHeight, bitsPerComponent, bitsPerPixel, bytesPerRow, YYCGColorSpaceGetDeviceRGB(), bitmapInfo, provider, NULL, false, kCGRenderingIntentDefault);
-
- CFRelease(provider);
- if (iterInited) WebPDemuxReleaseIterator(&iter);
- WebPDemuxDelete(demuxer);
-
- return imageRef;
-
- fail:
- if (destBytes) free(destBytes);
- if (provider) CFRelease(provider);
- if (iterInited) WebPDemuxReleaseIterator(&iter);
- if (demuxer) WebPDemuxDelete(demuxer);
- return NULL;
- }
- #else
- BOOL YYImageWebPAvailable() {
- return NO;
- }
- CFDataRef YYCGImageCreateEncodedWebPData(CGImageRef imageRef, BOOL lossless, CGFloat quality, int compressLevel, YYImagePreset preset) {
- NSLog(@"WebP decoder is disabled");
- return NULL;
- }
- NSUInteger YYImageGetWebPFrameCount(CFDataRef webpData) {
- NSLog(@"WebP decoder is disabled");
- return 0;
- }
- CGImageRef YYCGImageCreateWithWebPData(CFDataRef webpData,
- BOOL decodeForDisplay,
- BOOL useThreads,
- BOOL bypassFiltering,
- BOOL noFancyUpsampling) {
- NSLog(@"WebP decoder is disabled");
- return NULL;
- }
- #endif
- ////////////////////////////////////////////////////////////////////////////////
- #pragma mark - Decoder
- @implementation YYImageFrame
- + (instancetype)frameWithImage:(UIImage *)image {
- YYImageFrame *frame = [self new];
- frame.image = image;
- return frame;
- }
- - (id)copyWithZone:(NSZone *)zone {
- YYImageFrame *frame = [self.class new];
- frame.index = _index;
- frame.width = _width;
- frame.height = _height;
- frame.offsetX = _offsetX;
- frame.offsetY = _offsetY;
- frame.duration = _duration;
- frame.dispose = _dispose;
- frame.blend = _blend;
- frame.image = _image.copy;
- return frame;
- }
- @end
- // Internal frame object.
- @interface _YYImageDecoderFrame : YYImageFrame
- @property (nonatomic, assign) BOOL hasAlpha; ///< Whether frame has alpha.
- @property (nonatomic, assign) BOOL isFullSize; ///< Whether frame fill the canvas.
- @property (nonatomic, assign) NSUInteger blendFromIndex; ///< Blend from frame index to current frame.
- @end
- @implementation _YYImageDecoderFrame
- - (id)copyWithZone:(NSZone *)zone {
- _YYImageDecoderFrame *frame = [super copyWithZone:zone];
- frame.hasAlpha = _hasAlpha;
- frame.isFullSize = _isFullSize;
- frame.blendFromIndex = _blendFromIndex;
- return frame;
- }
- @end
- @implementation YYImageDecoder {
- pthread_mutex_t _lock; // recursive lock
-
- BOOL _sourceTypeDetected;
- CGImageSourceRef _source;
- yy_png_info *_apngSource;
- #if YYIMAGE_WEBP_ENABLED
- WebPDemuxer *_webpSource;
- #endif
-
- UIImageOrientation _orientation;
- dispatch_semaphore_t _framesLock;
- NSArray *_frames; ///< Array<GGImageDecoderFrame>, without image
- BOOL _needBlend;
- NSUInteger _blendFrameIndex;
- CGContextRef _blendCanvas;
- }
- - (void)dealloc {
- if (_source) CFRelease(_source);
- if (_apngSource) yy_png_info_release(_apngSource);
- #if YYIMAGE_WEBP_ENABLED
- if (_webpSource) WebPDemuxDelete(_webpSource);
- #endif
- if (_blendCanvas) CFRelease(_blendCanvas);
- pthread_mutex_destroy(&_lock);
- }
- + (instancetype)decoderWithData:(NSData *)data scale:(CGFloat)scale {
- if (!data) return nil;
- YYImageDecoder *decoder = [[YYImageDecoder alloc] initWithScale:scale];
- [decoder updateData:data final:YES];
- if (decoder.frameCount == 0) return nil;
- return decoder;
- }
- - (instancetype)init {
- return [self initWithScale:[UIScreen mainScreen].scale];
- }
- - (instancetype)initWithScale:(CGFloat)scale {
- self = [super init];
- if (scale <= 0) scale = 1;
- _scale = scale;
- _framesLock = dispatch_semaphore_create(1);
- pthread_mutex_init_recursive(&_lock, true);
- return self;
- }
- - (BOOL)updateData:(NSData *)data final:(BOOL)final {
- BOOL result = NO;
- pthread_mutex_lock(&_lock);
- result = [self _updateData:data final:final];
- pthread_mutex_unlock(&_lock);
- return result;
- }
- - (YYImageFrame *)frameAtIndex:(NSUInteger)index decodeForDisplay:(BOOL)decodeForDisplay {
- YYImageFrame *result = nil;
- pthread_mutex_lock(&_lock);
- result = [self _frameAtIndex:index decodeForDisplay:decodeForDisplay];
- pthread_mutex_unlock(&_lock);
- return result;
- }
- - (NSTimeInterval)frameDurationAtIndex:(NSUInteger)index {
- NSTimeInterval result = 0;
- dispatch_semaphore_wait(_framesLock, DISPATCH_TIME_FOREVER);
- if (index < _frames.count) {
- result = ((_YYImageDecoderFrame *)_frames[index]).duration;
- }
- dispatch_semaphore_signal(_framesLock);
- return result;
- }
- - (NSDictionary *)framePropertiesAtIndex:(NSUInteger)index {
- NSDictionary *result = nil;
- pthread_mutex_lock(&_lock);
- result = [self _framePropertiesAtIndex:index];
- pthread_mutex_unlock(&_lock);
- return result;
- }
- - (NSDictionary *)imageProperties {
- NSDictionary *result = nil;
- pthread_mutex_lock(&_lock);
- result = [self _imageProperties];
- pthread_mutex_unlock(&_lock);
- return result;
- }
- #pragma private (wrap)
- - (BOOL)_updateData:(NSData *)data final:(BOOL)final {
- if (_finalized) return NO;
- if (data.length < _data.length) return NO;
- _finalized = final;
- _data = data;
-
- YYImageType type = YYImageDetectType((__bridge CFDataRef)data);
- if (_sourceTypeDetected) {
- if (_type != type) {
- return NO;
- } else {
- [self _updateSource];
- }
- } else {
- if (_data.length > 16) {
- _type = type;
- _sourceTypeDetected = YES;
- [self _updateSource];
- }
- }
- return YES;
- }
- - (YYImageFrame *)_frameAtIndex:(NSUInteger)index decodeForDisplay:(BOOL)decodeForDisplay {
- if (index >= _frames.count) return 0;
- _YYImageDecoderFrame *frame = [(_YYImageDecoderFrame *)_frames[index] copy];
- BOOL decoded = NO;
- BOOL extendToCanvas = NO;
- if (_type != YYImageTypeICO && decodeForDisplay) { // ICO contains multi-size frame and should not extend to canvas.
- extendToCanvas = YES;
- }
-
- if (!_needBlend) {
- CGImageRef imageRef = [self _newUnblendedImageAtIndex:index extendToCanvas:extendToCanvas decoded:&decoded];
- if (!imageRef) return nil;
- if (decodeForDisplay && !decoded) {
- CGImageRef imageRefDecoded = YYCGImageCreateDecodedCopy(imageRef, YES);
- if (imageRefDecoded) {
- CFRelease(imageRef);
- imageRef = imageRefDecoded;
- decoded = YES;
- }
- }
- UIImage *image = [UIImage imageWithCGImage:imageRef scale:_scale orientation:_orientation];
- CFRelease(imageRef);
- if (!image) return nil;
- image.isDecodedForDisplay = decoded;
- frame.image = image;
- return frame;
- }
-
- // blend
- if (![self _createBlendContextIfNeeded]) return nil;
- CGImageRef imageRef = NULL;
-
- if (_blendFrameIndex + 1 == frame.index) {
- imageRef = [self _newBlendedImageWithFrame:frame];
- _blendFrameIndex = index;
- } else { // should draw canvas from previous frame
- _blendFrameIndex = NSNotFound;
- CGContextClearRect(_blendCanvas, CGRectMake(0, 0, _width, _height));
-
- if (frame.blendFromIndex == frame.index) {
- CGImageRef unblendedImage = [self _newUnblendedImageAtIndex:index extendToCanvas:NO decoded:NULL];
- if (unblendedImage) {
- CGContextDrawImage(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height), unblendedImage);
- CFRelease(unblendedImage);
- }
- imageRef = CGBitmapContextCreateImage(_blendCanvas);
- if (frame.dispose == YYImageDisposeBackground) {
- CGContextClearRect(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height));
- }
- _blendFrameIndex = index;
- } else { // canvas is not ready
- for (uint32_t i = (uint32_t)frame.blendFromIndex; i <= (uint32_t)frame.index; i++) {
- if (i == frame.index) {
- if (!imageRef) imageRef = [self _newBlendedImageWithFrame:frame];
- } else {
- [self _blendImageWithFrame:_frames[i]];
- }
- }
- _blendFrameIndex = index;
- }
- }
-
- if (!imageRef) return nil;
- UIImage *image = [UIImage imageWithCGImage:imageRef scale:_scale orientation:_orientation];
- CFRelease(imageRef);
- if (!image) return nil;
-
- image.isDecodedForDisplay = YES;
- frame.image = image;
- if (extendToCanvas) {
- frame.width = _width;
- frame.height = _height;
- frame.offsetX = 0;
- frame.offsetY = 0;
- frame.dispose = YYImageDisposeNone;
- frame.blend = YYImageBlendNone;
- }
- return frame;
- }
- - (NSDictionary *)_framePropertiesAtIndex:(NSUInteger)index {
- if (index >= _frames.count) return nil;
- if (!_source) return nil;
- CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(_source, index, NULL);
- if (!properties) return nil;
- return CFBridgingRelease(properties);
- }
- - (NSDictionary *)_imageProperties {
- if (!_source) return nil;
- CFDictionaryRef properties = CGImageSourceCopyProperties(_source, NULL);
- if (!properties) return nil;
- return CFBridgingRelease(properties);
- }
- #pragma private
- - (void)_updateSource {
- switch (_type) {
- case YYImageTypeWebP: {
- [self _updateSourceWebP];
- } break;
-
- case YYImageTypePNG: {
- [self _updateSourceAPNG];
- } break;
-
- default: {
- [self _updateSourceImageIO];
- } break;
- }
- }
- - (void)_updateSourceWebP {
- #if YYIMAGE_WEBP_ENABLED
- _width = 0;
- _height = 0;
- _loopCount = 0;
- if (_webpSource) WebPDemuxDelete(_webpSource);
- _webpSource = NULL;
- dispatch_semaphore_wait(_framesLock, DISPATCH_TIME_FOREVER);
- _frames = nil;
- dispatch_semaphore_signal(_framesLock);
-
- /*
- https://developers.google.com/speed/webp/docs/api
- The documentation said we can use WebPIDecoder to decode webp progressively,
- but currently it can only returns an empty image (not same as progressive jpegs),
- so we don't use progressive decoding.
-
- When using WebPDecode() to decode multi-frame webp, we will get the error
- "VP8_STATUS_UNSUPPORTED_FEATURE", so we first use WebPDemuxer to unpack it.
- */
-
- WebPData webPData = {0};
- webPData.bytes = _data.bytes;
- webPData.size = _data.length;
- WebPDemuxer *demuxer = WebPDemux(&webPData);
- if (!demuxer) return;
-
- uint32_t webpFrameCount = WebPDemuxGetI(demuxer, WEBP_FF_FRAME_COUNT);
- uint32_t webpLoopCount = WebPDemuxGetI(demuxer, WEBP_FF_LOOP_COUNT);
- uint32_t canvasWidth = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_WIDTH);
- uint32_t canvasHeight = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_HEIGHT);
- if (webpFrameCount == 0 || canvasWidth < 1 || canvasHeight < 1) {
- WebPDemuxDelete(demuxer);
- return;
- }
-
- NSMutableArray *frames = [NSMutableArray new];
- BOOL needBlend = NO;
- uint32_t iterIndex = 0;
- uint32_t lastBlendIndex = 0;
- WebPIterator iter = {0};
- if (WebPDemuxGetFrame(demuxer, 1, &iter)) { // one-based index...
- do {
- _YYImageDecoderFrame *frame = [_YYImageDecoderFrame new];
- [frames addObject:frame];
- if (iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) {
- frame.dispose = YYImageDisposeBackground;
- }
- if (iter.blend_method == WEBP_MUX_BLEND) {
- frame.blend = YYImageBlendOver;
- }
-
- int canvasWidth = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_WIDTH);
- int canvasHeight = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_HEIGHT);
- frame.index = iterIndex;
- frame.duration = iter.duration / 1000.0;
- frame.width = iter.width;
- frame.height = iter.height;
- frame.hasAlpha = iter.has_alpha;
- frame.blend = iter.blend_method == WEBP_MUX_BLEND;
- frame.offsetX = iter.x_offset;
- frame.offsetY = canvasHeight - iter.y_offset - iter.height;
-
- BOOL sizeEqualsToCanvas = (iter.width == canvasWidth && iter.height == canvasHeight);
- BOOL offsetIsZero = (iter.x_offset == 0 && iter.y_offset == 0);
- frame.isFullSize = (sizeEqualsToCanvas && offsetIsZero);
-
- if ((!frame.blend || !frame.hasAlpha) && frame.isFullSize) {
- frame.blendFromIndex = lastBlendIndex = iterIndex;
- } else {
- if (frame.dispose && frame.isFullSize) {
- frame.blendFromIndex = lastBlendIndex;
- lastBlendIndex = iterIndex + 1;
- } else {
- frame.blendFromIndex = lastBlendIndex;
- }
- }
- if (frame.index != frame.blendFromIndex) needBlend = YES;
- iterIndex++;
- } while (WebPDemuxNextFrame(&iter));
- WebPDemuxReleaseIterator(&iter);
- }
- if (frames.count != webpFrameCount) {
- WebPDemuxDelete(demuxer);
- return;
- }
-
- _width = canvasWidth;
- _height = canvasHeight;
- _frameCount = frames.count;
- _loopCount = webpLoopCount;
- _needBlend = needBlend;
- _webpSource = demuxer;
- dispatch_semaphore_wait(_framesLock, DISPATCH_TIME_FOREVER);
- _frames = frames;
- dispatch_semaphore_signal(_framesLock);
- #else
- static const char *func = __FUNCTION__;
- static const int line = __LINE__;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- NSLog(@"[%s: %d] WebP is not available, check the documentation to see how to install WebP component: https://github.com/ibireme/YYImage#installation", func, line);
- });
- #endif
- }
- - (void)_updateSourceAPNG {
- /*
- APNG extends PNG format to support animation, it was supported by ImageIO
- since iOS 8.
-
- We use a custom APNG decoder to make APNG available in old system, so we
- ignore the ImageIO's APNG frame info. Typically the custom decoder is a bit
- faster than ImageIO.
- */
-
- yy_png_info_release(_apngSource);
- _apngSource = nil;
-
- [self _updateSourceImageIO]; // decode first frame
- if (_frameCount == 0) return; // png decode failed
- if (!_finalized) return; // ignore multi-frame before finalized
-
- yy_png_info *apng = yy_png_info_create(_data.bytes, (uint32_t)_data.length);
- if (!apng) return; // apng decode failed
- if (apng->apng_frame_num == 0 ||
- (apng->apng_frame_num == 1 && apng->apng_first_frame_is_cover)) {
- yy_png_info_release(apng);
- return; // no animation
- }
- if (_source) { // apng decode succeed, no longer need image souce
- CFRelease(_source);
- _source = NULL;
- }
-
- uint32_t canvasWidth = apng->header.width;
- uint32_t canvasHeight = apng->header.height;
- NSMutableArray *frames = [NSMutableArray new];
- BOOL needBlend = NO;
- uint32_t lastBlendIndex = 0;
- for (uint32_t i = 0; i < apng->apng_frame_num; i++) {
- _YYImageDecoderFrame *frame = [_YYImageDecoderFrame new];
- [frames addObject:frame];
-
- yy_png_frame_info *fi = apng->apng_frames + i;
- frame.index = i;
- frame.duration = yy_png_delay_to_seconds(fi->frame_control.delay_num, fi->frame_control.delay_den);
- frame.hasAlpha = YES;
- frame.width = fi->frame_control.width;
- frame.height = fi->frame_control.height;
- frame.offsetX = fi->frame_control.x_offset;
- frame.offsetY = canvasHeight - fi->frame_control.y_offset - fi->frame_control.height;
-
- BOOL sizeEqualsToCanvas = (frame.width == canvasWidth && frame.height == canvasHeight);
- BOOL offsetIsZero = (fi->frame_control.x_offset == 0 && fi->frame_control.y_offset == 0);
- frame.isFullSize = (sizeEqualsToCanvas && offsetIsZero);
-
- switch (fi->frame_control.dispose_op) {
- case YY_PNG_DISPOSE_OP_BACKGROUND: {
- frame.dispose = YYImageDisposeBackground;
- } break;
- case YY_PNG_DISPOSE_OP_PREVIOUS: {
- frame.dispose = YYImageDisposePrevious;
- } break;
- default: {
- frame.dispose = YYImageDisposeNone;
- } break;
- }
- switch (fi->frame_control.blend_op) {
- case YY_PNG_BLEND_OP_OVER: {
- frame.blend = YYImageBlendOver;
- } break;
-
- default: {
- frame.blend = YYImageBlendNone;
- } break;
- }
-
- if (frame.blend == YYImageBlendNone && frame.isFullSize) {
- frame.blendFromIndex = i;
- if (frame.dispose != YYImageDisposePrevious) lastBlendIndex = i;
- } else {
- if (frame.dispose == YYImageDisposeBackground && frame.isFullSize) {
- frame.blendFromIndex = lastBlendIndex;
- lastBlendIndex = i + 1;
- } else {
- frame.blendFromIndex = lastBlendIndex;
- }
- }
- if (frame.index != frame.blendFromIndex) needBlend = YES;
- }
-
- _width = canvasWidth;
- _height = canvasHeight;
- _frameCount = frames.count;
- _loopCount = apng->apng_loop_num;
- _needBlend = needBlend;
- _apngSource = apng;
- dispatch_semaphore_wait(_framesLock, DISPATCH_TIME_FOREVER);
- _frames = frames;
- dispatch_semaphore_signal(_framesLock);
- }
- - (void)_updateSourceImageIO {
- _width = 0;
- _height = 0;
- _orientation = UIImageOrientationUp;
- _loopCount = 0;
- dispatch_semaphore_wait(_framesLock, DISPATCH_TIME_FOREVER);
- _frames = nil;
- dispatch_semaphore_signal(_framesLock);
-
- if (!_source) {
- if (_finalized) {
- _source = CGImageSourceCreateWithData((__bridge CFDataRef)_data, NULL);
- } else {
- _source = CGImageSourceCreateIncremental(NULL);
- if (_source) CGImageSourceUpdateData(_source, (__bridge CFDataRef)_data, false);
- }
- } else {
- CGImageSourceUpdateData(_source, (__bridge CFDataRef)_data, _finalized);
- }
- if (!_source) return;
-
- _frameCount = CGImageSourceGetCount(_source);
- if (_frameCount == 0) return;
-
- if (!_finalized) { // ignore multi-frame before finalized
- _frameCount = 1;
- } else {
- if (_type == YYImageTypePNG) { // use custom apng decoder and ignore multi-frame
- _frameCount = 1;
- }
- if (_type == YYImageTypeGIF) { // get gif loop count
- CFDictionaryRef properties = CGImageSourceCopyProperties(_source, NULL);
- if (properties) {
- CFDictionaryRef gif = CFDictionaryGetValue(properties, kCGImagePropertyGIFDictionary);
- if (gif) {
- CFTypeRef loop = CFDictionaryGetValue(gif, kCGImagePropertyGIFLoopCount);
- if (loop) CFNumberGetValue(loop, kCFNumberNSIntegerType, &_loopCount);
- }
- CFRelease(properties);
- }
- }
- }
-
- /*
- ICO, GIF, APNG may contains multi-frame.
- */
- NSMutableArray *frames = [NSMutableArray new];
- for (NSUInteger i = 0; i < _frameCount; i++) {
- _YYImageDecoderFrame *frame = [_YYImageDecoderFrame new];
- frame.index = i;
- frame.blendFromIndex = i;
- frame.hasAlpha = YES;
- frame.isFullSize = YES;
- [frames addObject:frame];
-
- CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(_source, i, NULL);
- if (properties) {
- NSTimeInterval duration = 0;
- NSInteger orientationValue = 0, width = 0, height = 0;
- CFTypeRef value = NULL;
-
- value = CFDictionaryGetValue(properties, kCGImagePropertyPixelWidth);
- if (value) CFNumberGetValue(value, kCFNumberNSIntegerType, &width);
- value = CFDictionaryGetValue(properties, kCGImagePropertyPixelHeight);
- if (value) CFNumberGetValue(value, kCFNumberNSIntegerType, &height);
- if (_type == YYImageTypeGIF) {
- CFDictionaryRef gif = CFDictionaryGetValue(properties, kCGImagePropertyGIFDictionary);
- if (gif) {
- // Use the unclamped frame delay if it exists.
- value = CFDictionaryGetValue(gif, kCGImagePropertyGIFUnclampedDelayTime);
- if (!value) {
- // Fall back to the clamped frame delay if the unclamped frame delay does not exist.
- value = CFDictionaryGetValue(gif, kCGImagePropertyGIFDelayTime);
- }
- if (value) CFNumberGetValue(value, kCFNumberDoubleType, &duration);
- }
- }
-
- frame.width = width;
- frame.height = height;
- frame.duration = duration;
-
- if (i == 0 && _width + _height == 0) { // init first frame
- _width = width;
- _height = height;
- value = CFDictionaryGetValue(properties, kCGImagePropertyOrientation);
- if (value) {
- CFNumberGetValue(value, kCFNumberNSIntegerType, &orientationValue);
- _orientation = YYUIImageOrientationFromEXIFValue(orientationValue);
- }
- }
- CFRelease(properties);
- }
- }
- dispatch_semaphore_wait(_framesLock, DISPATCH_TIME_FOREVER);
- _frames = frames;
- dispatch_semaphore_signal(_framesLock);
- }
- - (CGImageRef)_newUnblendedImageAtIndex:(NSUInteger)index
- extendToCanvas:(BOOL)extendToCanvas
- decoded:(BOOL *)decoded CF_RETURNS_RETAINED {
-
- if (!_finalized && index > 0) return NULL;
- if (_frames.count <= index) return NULL;
- _YYImageDecoderFrame *frame = _frames[index];
-
- if (_source) {
- CGImageRef imageRef = CGImageSourceCreateImageAtIndex(_source, index, (CFDictionaryRef)@{(id)kCGImageSourceShouldCache:@(YES)});
- if (imageRef && extendToCanvas) {
- size_t width = CGImageGetWidth(imageRef);
- size_t height = CGImageGetHeight(imageRef);
- if (width == _width && height == _height) {
- CGImageRef imageRefExtended = YYCGImageCreateDecodedCopy(imageRef, YES);
- if (imageRefExtended) {
- CFRelease(imageRef);
- imageRef = imageRefExtended;
- if (decoded) *decoded = YES;
- }
- } else {
- CGContextRef context = CGBitmapContextCreate(NULL, _width, _height, 8, 0, YYCGColorSpaceGetDeviceRGB(), kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst);
- if (context) {
- CGContextDrawImage(context, CGRectMake(0, _height - height, width, height), imageRef);
- CGImageRef imageRefExtended = CGBitmapContextCreateImage(context);
- CFRelease(context);
- if (imageRefExtended) {
- CFRelease(imageRef);
- imageRef = imageRefExtended;
- if (decoded) *decoded = YES;
- }
- }
- }
- }
- return imageRef;
- }
-
- if (_apngSource) {
- uint32_t size = 0;
- uint8_t *bytes = yy_png_copy_frame_data_at_index(_data.bytes, _apngSource, (uint32_t)index, &size);
- if (!bytes) return NULL;
- CGDataProviderRef provider = CGDataProviderCreateWithData(bytes, bytes, size, YYCGDataProviderReleaseDataCallback);
- if (!provider) {
- free(bytes);
- return NULL;
- }
- bytes = NULL; // hold by provider
-
- CGImageSourceRef source = CGImageSourceCreateWithDataProvider(provider, NULL);
- if (!source) {
- CFRelease(provider);
- return NULL;
- }
- CFRelease(provider);
-
- if(CGImageSourceGetCount(source) < 1) {
- CFRelease(source);
- return NULL;
- }
-
- CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, 0, (CFDictionaryRef)@{(id)kCGImageSourceShouldCache:@(YES)});
- CFRelease(source);
- if (!imageRef) return NULL;
- if (extendToCanvas) {
- CGContextRef context = CGBitmapContextCreate(NULL, _width, _height, 8, 0, YYCGColorSpaceGetDeviceRGB(), kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst); //bgrA
- if (context) {
- CGContextDrawImage(context, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height), imageRef);
- CFRelease(imageRef);
- imageRef = CGBitmapContextCreateImage(context);
- CFRelease(context);
- if (decoded) *decoded = YES;
- }
- }
- return imageRef;
- }
-
- #if YYIMAGE_WEBP_ENABLED
- if (_webpSource) {
- WebPIterator iter;
- if (!WebPDemuxGetFrame(_webpSource, (int)(index + 1), &iter)) return NULL; // demux webp frame data
- // frame numbers are one-based in webp -----------^
-
- int frameWidth = iter.width;
- int frameHeight = iter.height;
- if (frameWidth < 1 || frameHeight < 1) return NULL;
-
- int width = extendToCanvas ? (int)_width : frameWidth;
- int height = extendToCanvas ? (int)_height : frameHeight;
- if (width > _width || height > _height) return NULL;
-
- const uint8_t *payload = iter.fragment.bytes;
- size_t payloadSize = iter.fragment.size;
-
- WebPDecoderConfig config;
- if (!WebPInitDecoderConfig(&config)) {
- WebPDemuxReleaseIterator(&iter);
- return NULL;
- }
- if (WebPGetFeatures(payload , payloadSize, &config.input) != VP8_STATUS_OK) {
- WebPDemuxReleaseIterator(&iter);
- return NULL;
- }
-
- size_t bitsPerComponent = 8;
- size_t bitsPerPixel = 32;
- size_t bytesPerRow = YYImageByteAlign(bitsPerPixel / 8 * width, 32);
- size_t length = bytesPerRow * height;
- CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst; //bgrA
-
- void *pixels = calloc(1, length);
- if (!pixels) {
- WebPDemuxReleaseIterator(&iter);
- return NULL;
- }
-
- config.output.colorspace = MODE_bgrA;
- config.output.is_external_memory = 1;
- config.output.u.RGBA.rgba = pixels;
- config.output.u.RGBA.stride = (int)bytesPerRow;
- config.output.u.RGBA.size = length;
- VP8StatusCode result = WebPDecode(payload, payloadSize, &config); // decode
- if ((result != VP8_STATUS_OK) && (result != VP8_STATUS_NOT_ENOUGH_DATA)) {
- WebPDemuxReleaseIterator(&iter);
- free(pixels);
- return NULL;
- }
- WebPDemuxReleaseIterator(&iter);
-
- if (extendToCanvas && (iter.x_offset != 0 || iter.y_offset != 0)) {
- void *tmp = calloc(1, length);
- if (tmp) {
- vImage_Buffer src = {pixels, height, width, bytesPerRow};
- vImage_Buffer dest = {tmp, height, width, bytesPerRow};
- vImage_CGAffineTransform transform = {1, 0, 0, 1, iter.x_offset, -iter.y_offset};
- uint8_t backColor[4] = {0};
- vImage_Error error = vImageAffineWarpCG_ARGB8888(&src, &dest, NULL, &transform, backColor, kvImageBackgroundColorFill);
- if (error == kvImageNoError) {
- memcpy(pixels, tmp, length);
- }
- free(tmp);
- }
- }
-
- CGDataProviderRef provider = CGDataProviderCreateWithData(pixels, pixels, length, YYCGDataProviderReleaseDataCallback);
- if (!provider) {
- free(pixels);
- return NULL;
- }
- pixels = NULL; // hold by provider
-
- CGImageRef image = CGImageCreate(width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, YYCGColorSpaceGetDeviceRGB(), bitmapInfo, provider, NULL, false, kCGRenderingIntentDefault);
- CFRelease(provider);
- if (decoded) *decoded = YES;
- return image;
- }
- #endif
-
- return NULL;
- }
- - (BOOL)_createBlendContextIfNeeded {
- if (!_blendCanvas) {
- _blendFrameIndex = NSNotFound;
- _blendCanvas = CGBitmapContextCreate(NULL, _width, _height, 8, 0, YYCGColorSpaceGetDeviceRGB(), kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst);
- }
- BOOL suc = _blendCanvas != NULL;
- return suc;
- }
- - (void)_blendImageWithFrame:(_YYImageDecoderFrame *)frame {
- if (frame.dispose == YYImageDisposePrevious) {
- // nothing
- } else if (frame.dispose == YYImageDisposeBackground) {
- CGContextClearRect(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height));
- } else { // no dispose
- if (frame.blend == YYImageBlendOver) {
- CGImageRef unblendImage = [self _newUnblendedImageAtIndex:frame.index extendToCanvas:NO decoded:NULL];
- if (unblendImage) {
- CGContextDrawImage(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height), unblendImage);
- CFRelease(unblendImage);
- }
- } else {
- CGContextClearRect(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height));
- CGImageRef unblendImage = [self _newUnblendedImageAtIndex:frame.index extendToCanvas:NO decoded:NULL];
- if (unblendImage) {
- CGContextDrawImage(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height), unblendImage);
- CFRelease(unblendImage);
- }
- }
- }
- }
- - (CGImageRef)_newBlendedImageWithFrame:(_YYImageDecoderFrame *)frame CF_RETURNS_RETAINED{
- CGImageRef imageRef = NULL;
- if (frame.dispose == YYImageDisposePrevious) {
- if (frame.blend == YYImageBlendOver) {
- CGImageRef previousImage = CGBitmapContextCreateImage(_blendCanvas);
- CGImageRef unblendImage = [self _newUnblendedImageAtIndex:frame.index extendToCanvas:NO decoded:NULL];
- if (unblendImage) {
- CGContextDrawImage(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height), unblendImage);
- CFRelease(unblendImage);
- }
- imageRef = CGBitmapContextCreateImage(_blendCanvas);
- CGContextClearRect(_blendCanvas, CGRectMake(0, 0, _width, _height));
- if (previousImage) {
- CGContextDrawImage(_blendCanvas, CGRectMake(0, 0, _width, _height), previousImage);
- CFRelease(previousImage);
- }
- } else {
- CGImageRef previousImage = CGBitmapContextCreateImage(_blendCanvas);
- CGImageRef unblendImage = [self _newUnblendedImageAtIndex:frame.index extendToCanvas:NO decoded:NULL];
- if (unblendImage) {
- CGContextClearRect(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height));
- CGContextDrawImage(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height), unblendImage);
- CFRelease(unblendImage);
- }
- imageRef = CGBitmapContextCreateImage(_blendCanvas);
- CGContextClearRect(_blendCanvas, CGRectMake(0, 0, _width, _height));
- if (previousImage) {
- CGContextDrawImage(_blendCanvas, CGRectMake(0, 0, _width, _height), previousImage);
- CFRelease(previousImage);
- }
- }
- } else if (frame.dispose == YYImageDisposeBackground) {
- if (frame.blend == YYImageBlendOver) {
- CGImageRef unblendImage = [self _newUnblendedImageAtIndex:frame.index extendToCanvas:NO decoded:NULL];
- if (unblendImage) {
- CGContextDrawImage(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height), unblendImage);
- CFRelease(unblendImage);
- }
- imageRef = CGBitmapContextCreateImage(_blendCanvas);
- CGContextClearRect(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height));
- } else {
- CGImageRef unblendImage = [self _newUnblendedImageAtIndex:frame.index extendToCanvas:NO decoded:NULL];
- if (unblendImage) {
- CGContextClearRect(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height));
- CGContextDrawImage(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height), unblendImage);
- CFRelease(unblendImage);
- }
- imageRef = CGBitmapContextCreateImage(_blendCanvas);
- CGContextClearRect(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height));
- }
- } else { // no dispose
- if (frame.blend == YYImageBlendOver) {
- CGImageRef unblendImage = [self _newUnblendedImageAtIndex:frame.index extendToCanvas:NO decoded:NULL];
- if (unblendImage) {
- CGContextDrawImage(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height), unblendImage);
- CFRelease(unblendImage);
- }
- imageRef = CGBitmapContextCreateImage(_blendCanvas);
- } else {
- CGImageRef unblendImage = [self _newUnblendedImageAtIndex:frame.index extendToCanvas:NO decoded:NULL];
- if (unblendImage) {
- CGContextClearRect(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height));
- CGContextDrawImage(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height), unblendImage);
- CFRelease(unblendImage);
- }
- imageRef = CGBitmapContextCreateImage(_blendCanvas);
- }
- }
- return imageRef;
- }
- @end
- ////////////////////////////////////////////////////////////////////////////////
- #pragma mark - Encoder
- @implementation YYImageEncoder {
- NSMutableArray *_images;
- NSMutableArray *_durations;
- }
- - (instancetype)init {
- @throw [NSException exceptionWithName:@"YYImageEncoder init error" reason:@"YYImageEncoder must be initialized with a type. Use 'initWithType:' instead." userInfo:nil];
- return [self initWithType:YYImageTypeUnknown];
- }
- - (instancetype)initWithType:(YYImageType)type {
- if (type == YYImageTypeUnknown || type >= YYImageTypeOther) {
- NSLog(@"[%s: %d] Unsupported image type:%d",__FUNCTION__, __LINE__, (int)type);
- return nil;
- }
-
- #if !YYIMAGE_WEBP_ENABLED
- if (type == YYImageTypeWebP) {
- NSLog(@"[%s: %d] WebP is not available, check the documentation to see how to install WebP component: https://github.com/ibireme/YYImage#installation", __FUNCTION__, __LINE__);
- return nil;
- }
- #endif
-
- self = [super init];
- if (!self) return nil;
- _type = type;
- _images = [NSMutableArray new];
- _durations = [NSMutableArray new];
- switch (type) {
- case YYImageTypeJPEG:
- case YYImageTypeJPEG2000: {
- _quality = 0.9;
- } break;
- case YYImageTypeTIFF:
- case YYImageTypeBMP:
- case YYImageTypeGIF:
- case YYImageTypeICO:
- case YYImageTypeICNS:
- case YYImageTypePNG: {
- _quality = 1;
- _lossless = YES;
- } break;
- case YYImageTypeWebP: {
- _quality = 0.8;
- } break;
- default:
- break;
- }
-
- return self;
- }
- - (void)setQuality:(CGFloat)quality {
- _quality = quality < 0 ? 0 : quality > 1 ? 1 : quality;
- }
- - (void)addImage:(UIImage *)image duration:(NSTimeInterval)duration {
- if (!image.CGImage) return;
- duration = duration < 0 ? 0 : duration;
- [_images addObject:image];
- [_durations addObject:@(duration)];
- }
- - (void)addImageWithData:(NSData *)data duration:(NSTimeInterval)duration {
- if (data.length == 0) return;
- duration = duration < 0 ? 0 : duration;
- [_images addObject:data];
- [_durations addObject:@(duration)];
- }
- - (void)addImageWithFile:(NSString *)path duration:(NSTimeInterval)duration {
- if (path.length == 0) return;
- duration = duration < 0 ? 0 : duration;
- NSURL *url = [NSURL URLWithString:path];
- if (!url) return;
- [_images addObject:url];
- [_durations addObject:@(duration)];
- }
- - (BOOL)_imageIOAvaliable {
- switch (_type) {
- case YYImageTypeJPEG:
- case YYImageTypeJPEG2000:
- case YYImageTypeTIFF:
- case YYImageTypeBMP:
- case YYImageTypeICO:
- case YYImageTypeICNS:
- case YYImageTypeGIF: {
- return _images.count > 0;
- } break;
- case YYImageTypePNG: {
- return _images.count == 1;
- } break;
- case YYImageTypeWebP: {
- return NO;
- } break;
- default: return NO;
- }
- }
- - (CGImageDestinationRef)_newImageDestination:(id)dest imageCount:(NSUInteger)count CF_RETURNS_RETAINED {
- if (!dest) return nil;
- CGImageDestinationRef destination = NULL;
- if ([dest isKindOfClass:[NSString class]]) {
- NSURL *url = [[NSURL alloc] initFileURLWithPath:dest];
- if (url) {
- destination = CGImageDestinationCreateWithURL((CFURLRef)url, YYImageTypeToUTType(_type), count, NULL);
- }
- } else if ([dest isKindOfClass:[NSMutableData class]]) {
- destination = CGImageDestinationCreateWithData((CFMutableDataRef)dest, YYImageTypeToUTType(_type), count, NULL);
- }
- return destination;
- }
- - (void)_encodeImageWithDestination:(CGImageDestinationRef)destination imageCount:(NSUInteger)count {
- if (_type == YYImageTypeGIF) {
- NSDictionary *gifProperty = @{(__bridge id)kCGImagePropertyGIFDictionary:
- @{(__bridge id)kCGImagePropertyGIFLoopCount: @(_loopCount)}};
- CGImageDestinationSetProperties(destination, (__bridge CFDictionaryRef)gifProperty);
- }
-
- for (int i = 0; i < count; i++) {
- @autoreleasepool {
- id imageSrc = _images[i];
- NSDictionary *frameProperty = NULL;
- if (_type == YYImageTypeGIF && count > 1) {
- frameProperty = @{(NSString *)kCGImagePropertyGIFDictionary : @{(NSString *) kCGImagePropertyGIFDelayTime:_durations[i]}};
- } else {
- frameProperty = @{(id)kCGImageDestinationLossyCompressionQuality : @(_quality)};
- }
-
- if ([imageSrc isKindOfClass:[UIImage class]]) {
- UIImage *image = imageSrc;
- if (image.imageOrientation != UIImageOrientationUp && image.CGImage) {
- CGBitmapInfo info = CGImageGetBitmapInfo(image.CGImage) | CGImageGetAlphaInfo(image.CGImage);
- CGImageRef rotated = YYCGImageCreateCopyWithOrientation(image.CGImage, image.imageOrientation, info);
- if (rotated) {
- image = [UIImage imageWithCGImage:rotated];
- CFRelease(rotated);
- }
- }
- if (image.CGImage) CGImageDestinationAddImage(destination, ((UIImage *)imageSrc).CGImage, (CFDictionaryRef)frameProperty);
- } else if ([imageSrc isKindOfClass:[NSURL class]]) {
- CGImageSourceRef source = CGImageSourceCreateWithURL((CFURLRef)imageSrc, NULL);
- if (source) {
- CGImageDestinationAddImageFromSource(destination, source, 0, (CFDictionaryRef)frameProperty);
- CFRelease(source);
- }
- } else if ([imageSrc isKindOfClass:[NSData class]]) {
- CGImageSourceRef source = CGImageSourceCreateWithData((CFDataRef)imageSrc, NULL);
- if (source) {
- CGImageDestinationAddImageFromSource(destination, source, 0, (CFDictionaryRef)frameProperty);
- CFRelease(source);
- }
- }
- }
- }
- }
- - (CGImageRef)_newCGImageFromIndex:(NSUInteger)index decoded:(BOOL)decoded CF_RETURNS_RETAINED {
- UIImage *image = nil;
- id imageSrc= _images[index];
- if ([imageSrc isKindOfClass:[UIImage class]]) {
- image = imageSrc;
- } else if ([imageSrc isKindOfClass:[NSURL class]]) {
- image = [UIImage imageWithContentsOfFile:((NSURL *)imageSrc).absoluteString];
- } else if ([imageSrc isKindOfClass:[NSData class]]) {
- image = [UIImage imageWithData:imageSrc];
- }
- if (!image) return NULL;
- CGImageRef imageRef = image.CGImage;
- if (!imageRef) return NULL;
- if (image.imageOrientation != UIImageOrientationUp) {
- return YYCGImageCreateCopyWithOrientation(imageRef, image.imageOrientation, kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst);
- }
- if (decoded) {
- return YYCGImageCreateDecodedCopy(imageRef, YES);
- }
- return (CGImageRef)CFRetain(imageRef);
- }
- - (NSData *)_encodeWithImageIO {
- NSMutableData *data = [NSMutableData new];
- NSUInteger count = _type == YYImageTypeGIF ? _images.count : 1;
- CGImageDestinationRef destination = [self _newImageDestination:data imageCount:count];
- BOOL suc = NO;
- if (destination) {
- [self _encodeImageWithDestination:destination imageCount:count];
- suc = CGImageDestinationFinalize(destination);
- CFRelease(destination);
- }
- if (suc && data.length > 0) {
- return data;
- } else {
- return nil;
- }
- }
- - (BOOL)_encodeWithImageIO:(NSString *)path {
- NSUInteger count = _type == YYImageTypeGIF ? _images.count : 1;
- CGImageDestinationRef destination = [self _newImageDestination:path imageCount:count];
- BOOL suc = NO;
- if (destination) {
- [self _encodeImageWithDestination:destination imageCount:count];
- suc = CGImageDestinationFinalize(destination);
- CFRelease(destination);
- }
- return suc;
- }
- - (NSData *)_encodeAPNG {
- // encode APNG (ImageIO doesn't support APNG encoding, so we use a custom encoder)
- NSMutableArray *pngDatas = [NSMutableArray new];
- NSMutableArray *pngSizes = [NSMutableArray new];
- NSUInteger canvasWidth = 0, canvasHeight = 0;
- for (int i = 0; i < _images.count; i++) {
- CGImageRef decoded = [self _newCGImageFromIndex:i decoded:YES];
- if (!decoded) return nil;
- CGSize size = CGSizeMake(CGImageGetWidth(decoded), CGImageGetHeight(decoded));
- [pngSizes addObject:[NSValue valueWithCGSize:size]];
- if (canvasWidth < size.width) canvasWidth = size.width;
- if (canvasHeight < size.height) canvasHeight = size.height;
- CFDataRef frameData = YYCGImageCreateEncodedData(decoded, YYImageTypePNG, 1);
- CFRelease(decoded);
- if (!frameData) return nil;
- [pngDatas addObject:(__bridge id)(frameData)];
- CFRelease(frameData);
- if (size.width < 1 || size.height < 1) return nil;
- }
- CGSize firstFrameSize = [(NSValue *)[pngSizes firstObject] CGSizeValue];
- if (firstFrameSize.width < canvasWidth || firstFrameSize.height < canvasHeight) {
- CGImageRef decoded = [self _newCGImageFromIndex:0 decoded:YES];
- if (!decoded) return nil;
- CGContextRef context = CGBitmapContextCreate(NULL, canvasWidth, canvasHeight, 8,
- 0, YYCGColorSpaceGetDeviceRGB(), kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst);
- if (!context) {
- CFRelease(decoded);
- return nil;
- }
- CGContextDrawImage(context, CGRectMake(0, canvasHeight - firstFrameSize.height, firstFrameSize.width, firstFrameSize.height), decoded);
- CFRelease(decoded);
- CGImageRef extendedImage = CGBitmapContextCreateImage(context);
- CFRelease(context);
- if (!extendedImage) return nil;
- CFDataRef frameData = YYCGImageCreateEncodedData(extendedImage, YYImageTypePNG, 1);
- if (!frameData) {
- CFRelease(extendedImage);
- return nil;
- }
- pngDatas[0] = (__bridge id)(frameData);
- CFRelease(frameData);
- }
-
- NSData *firstFrameData = pngDatas[0];
- yy_png_info *info = yy_png_info_create(firstFrameData.bytes, (uint32_t)firstFrameData.length);
- if (!info) return nil;
- NSMutableData *result = [NSMutableData new];
- BOOL insertBefore = NO, insertAfter = NO;
- uint32_t apngSequenceIndex = 0;
-
- uint32_t png_header[2];
- png_header[0] = YY_FOUR_CC(0x89, 0x50, 0x4E, 0x47);
- png_header[1] = YY_FOUR_CC(0x0D, 0x0A, 0x1A, 0x0A);
-
- [result appendBytes:png_header length:8];
-
- for (int i = 0; i < info->chunk_num; i++) {
- yy_png_chunk_info *chunk = info->chunks + i;
-
- if (!insertBefore && chunk->fourcc == YY_FOUR_CC('I', 'D', 'A', 'T')) {
- insertBefore = YES;
- // insert acTL (APNG Control)
- uint32_t acTL[5] = {0};
- acTL[0] = yy_swap_endian_uint32(8); //length
- acTL[1] = YY_FOUR_CC('a', 'c', 'T', 'L'); // fourcc
- acTL[2] = yy_swap_endian_uint32((uint32_t)pngDatas.count); // num frames
- acTL[3] = yy_swap_endian_uint32((uint32_t)_loopCount); // num plays
- acTL[4] = yy_swap_endian_uint32((uint32_t)crc32(0, (const Bytef *)(acTL + 1), 12)); //crc32
- [result appendBytes:acTL length:20];
-
- // insert fcTL (first frame control)
- yy_png_chunk_fcTL chunk_fcTL = {0};
- chunk_fcTL.sequence_number = apngSequenceIndex;
- chunk_fcTL.width = (uint32_t)firstFrameSize.width;
- chunk_fcTL.height = (uint32_t)firstFrameSize.height;
- yy_png_delay_to_fraction([(NSNumber *)_durations[0] doubleValue], &chunk_fcTL.delay_num, &chunk_fcTL.delay_den);
- chunk_fcTL.delay_num = chunk_fcTL.delay_num;
- chunk_fcTL.delay_den = chunk_fcTL.delay_den;
- chunk_fcTL.dispose_op = YY_PNG_DISPOSE_OP_BACKGROUND;
- chunk_fcTL.blend_op = YY_PNG_BLEND_OP_SOURCE;
-
- uint8_t fcTL[38] = {0};
- *((uint32_t *)fcTL) = yy_swap_endian_uint32(26); //length
- *((uint32_t *)(fcTL + 4)) = YY_FOUR_CC('f', 'c', 'T', 'L'); // fourcc
- yy_png_chunk_fcTL_write(&chunk_fcTL, fcTL + 8);
- *((uint32_t *)(fcTL + 34)) = yy_swap_endian_uint32((uint32_t)crc32(0, (const Bytef *)(fcTL + 4), 30));
- [result appendBytes:fcTL length:38];
-
- apngSequenceIndex++;
- }
-
- if (!insertAfter && insertBefore && chunk->fourcc != YY_FOUR_CC('I', 'D', 'A', 'T')) {
- insertAfter = YES;
- // insert fcTL and fdAT (APNG frame control and data)
-
- for (int i = 1; i < pngDatas.count; i++) {
- NSData *frameData = pngDatas[i];
- yy_png_info *frame = yy_png_info_create(frameData.bytes, (uint32_t)frameData.length);
- if (!frame) {
- yy_png_info_release(info);
- return nil;
- }
-
- // insert fcTL (first frame control)
- yy_png_chunk_fcTL chunk_fcTL = {0};
- chunk_fcTL.sequence_number = apngSequenceIndex;
- chunk_fcTL.width = frame->header.width;
- chunk_fcTL.height = frame->header.height;
- yy_png_delay_to_fraction([(NSNumber *)_durations[i] doubleValue], &chunk_fcTL.delay_num, &chunk_fcTL.delay_den);
- chunk_fcTL.delay_num = chunk_fcTL.delay_num;
- chunk_fcTL.delay_den = chunk_fcTL.delay_den;
- chunk_fcTL.dispose_op = YY_PNG_DISPOSE_OP_BACKGROUND;
- chunk_fcTL.blend_op = YY_PNG_BLEND_OP_SOURCE;
-
- uint8_t fcTL[38] = {0};
- *((uint32_t *)fcTL) = yy_swap_endian_uint32(26); //length
- *((uint32_t *)(fcTL + 4)) = YY_FOUR_CC('f', 'c', 'T', 'L'); // fourcc
- yy_png_chunk_fcTL_write(&chunk_fcTL, fcTL + 8);
- *((uint32_t *)(fcTL + 34)) = yy_swap_endian_uint32((uint32_t)crc32(0, (const Bytef *)(fcTL + 4), 30));
- [result appendBytes:fcTL length:38];
-
- apngSequenceIndex++;
-
- // insert fdAT (frame data)
- for (int d = 0; d < frame->chunk_num; d++) {
- yy_png_chunk_info *dchunk = frame->chunks + d;
- if (dchunk->fourcc == YY_FOUR_CC('I', 'D', 'A', 'T')) {
- uint32_t length = yy_swap_endian_uint32(dchunk->length + 4);
- [result appendBytes:&length length:4]; //length
- uint32_t fourcc = YY_FOUR_CC('f', 'd', 'A', 'T');
- [result appendBytes:&fourcc length:4]; //fourcc
- uint32_t sq = yy_swap_endian_uint32(apngSequenceIndex);
- [result appendBytes:&sq length:4]; //data (sq)
- [result appendBytes:(((uint8_t *)frameData.bytes) + dchunk->offset + 8) length:dchunk->length]; //data
- uint8_t *bytes = ((uint8_t *)result.bytes) + result.length - dchunk->length - 8;
- uint32_t crc = yy_swap_endian_uint32((uint32_t)crc32(0, bytes, dchunk->length + 8));
- [result appendBytes:&crc length:4]; //crc
-
- apngSequenceIndex++;
- }
- }
- yy_png_info_release(frame);
- }
- }
-
- [result appendBytes:((uint8_t *)firstFrameData.bytes) + chunk->offset length:chunk->length + 12];
- }
- yy_png_info_release(info);
- return result;
- }
- - (NSData *)_encodeWebP {
- #if YYIMAGE_WEBP_ENABLED
- // encode webp
- NSMutableArray *webpDatas = [NSMutableArray new];
- for (NSUInteger i = 0; i < _images.count; i++) {
- CGImageRef image = [self _newCGImageFromIndex:i decoded:NO];
- if (!image) return nil;
- CFDataRef frameData = YYCGImageCreateEncodedWebPData(image, _lossless, _quality, 4, YYImagePresetDefault);
- CFRelease(image);
- if (!frameData) return nil;
- [webpDatas addObject:(__bridge id)frameData];
- CFRelease(frameData);
- }
- if (webpDatas.count == 1) {
- return webpDatas.firstObject;
- } else {
- // multi-frame webp
- WebPMux *mux = WebPMuxNew();
- if (!mux) return nil;
- for (NSUInteger i = 0; i < _images.count; i++) {
- NSData *data = webpDatas[i];
- NSNumber *duration = _durations[i];
- WebPMuxFrameInfo frame = {0};
- frame.bitstream.bytes = data.bytes;
- frame.bitstream.size = data.length;
- frame.duration = (int)(duration.floatValue * 1000.0);
- frame.id = WEBP_CHUNK_ANMF;
- frame.dispose_method = WEBP_MUX_DISPOSE_BACKGROUND;
- frame.blend_method = WEBP_MUX_NO_BLEND;
- if (WebPMuxPushFrame(mux, &frame, 0) != WEBP_MUX_OK) {
- WebPMuxDelete(mux);
- return nil;
- }
- }
-
- WebPMuxAnimParams params = {(uint32_t)0, (int)_loopCount};
- if (WebPMuxSetAnimationParams(mux, ¶ms) != WEBP_MUX_OK) {
- WebPMuxDelete(mux);
- return nil;
- }
-
- WebPData output_data;
- WebPMuxError error = WebPMuxAssemble(mux, &output_data);
- WebPMuxDelete(mux);
- if (error != WEBP_MUX_OK) {
- return nil;
- }
- NSData *result = [NSData dataWithBytes:output_data.bytes length:output_data.size];
- WebPDataClear(&output_data);
- return result.length ? result : nil;
- }
- #else
- return nil;
- #endif
- }
- - (NSData *)encode {
- if (_images.count == 0) return nil;
-
- if ([self _imageIOAvaliable]) return [self _encodeWithImageIO];
- if (_type == YYImageTypePNG) return [self _encodeAPNG];
- if (_type == YYImageTypeWebP) return [self _encodeWebP];
- return nil;
- }
- - (BOOL)encodeToFile:(NSString *)path {
- if (_images.count == 0 || path.length == 0) return NO;
-
- if ([self _imageIOAvaliable]) return [self _encodeWithImageIO:path];
- NSData *data = [self encode];
- if (!data) return NO;
- return [data writeToFile:path atomically:YES];
- }
- + (NSData *)encodeImage:(UIImage *)image type:(YYImageType)type quality:(CGFloat)quality {
- YYImageEncoder *encoder = [[YYImageEncoder alloc] initWithType:type];
- encoder.quality = quality;
- [encoder addImage:image duration:0];
- return [encoder encode];
- }
- + (NSData *)encodeImageWithDecoder:(YYImageDecoder *)decoder type:(YYImageType)type quality:(CGFloat)quality {
- if (!decoder || decoder.frameCount == 0) return nil;
- YYImageEncoder *encoder = [[YYImageEncoder alloc] initWithType:type];
- encoder.quality = quality;
- for (int i = 0; i < decoder.frameCount; i++) {
- UIImage *frame = [decoder frameAtIndex:i decodeForDisplay:YES].image;
- [encoder addImageWithData:UIImagePNGRepresentation(frame) duration:[decoder frameDurationAtIndex:i]];
- }
- return encoder.encode;
- }
- @end
- @implementation UIImage (YYImageCoder)
- - (instancetype)imageByDecoded {
- if (self.isDecodedForDisplay) return self;
- CGImageRef imageRef = self.CGImage;
- if (!imageRef) return self;
- CGImageRef newImageRef = YYCGImageCreateDecodedCopy(imageRef, YES);
- if (!newImageRef) return self;
- UIImage *newImage = [[self.class alloc] initWithCGImage:newImageRef scale:self.scale orientation:self.imageOrientation];
- CGImageRelease(newImageRef);
- if (!newImage) newImage = self; // decode failed, return self.
- newImage.isDecodedForDisplay = YES;
- return newImage;
- }
- - (BOOL)isDecodedForDisplay {
- if (self.images.count > 1) return YES;
- NSNumber *num = objc_getAssociatedObject(self, @selector(isDecodedForDisplay));
- return [num boolValue];
- }
- - (void)setIsDecodedForDisplay:(BOOL)isDecodedForDisplay {
- objc_setAssociatedObject(self, @selector(isDecodedForDisplay), @(isDecodedForDisplay), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
- }
- - (void)saveToAlbumWithCompletionBlock:(void(^)(NSURL *assetURL, NSError *error))completionBlock {
- dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
- NSData *data = [self _imageDataRepresentationForSystem:YES];
- ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
- [library writeImageDataToSavedPhotosAlbum:data metadata:nil completionBlock:^(NSURL *assetURL, NSError *error){
- if (!completionBlock) return;
- if (pthread_main_np()) {
- completionBlock(assetURL, error);
- } else {
- dispatch_async(dispatch_get_main_queue(), ^{
- completionBlock(assetURL, error);
- });
- }
- }];
- });
- }
- - (NSData *)imageDataRepresentation {
- return [self _imageDataRepresentationForSystem:NO];
- }
- /// @param forSystem YES: used for system album (PNG/JPEG/GIF), NO: used for YYImage (PNG/JPEG/GIF/WebP)
- - (NSData *)_imageDataRepresentationForSystem:(BOOL)forSystem {
- NSData *data = nil;
- if ([self isKindOfClass:[YYImage class]]) {
- YYImage *image = (id)self;
- if (image.animatedImageData) {
- if (forSystem) { // system only support GIF and PNG
- if (image.animatedImageType == YYImageTypeGIF ||
- image.animatedImageType == YYImageTypePNG) {
- data = image.animatedImageData;
- }
- } else {
- data = image.animatedImageData;
- }
- }
- }
- if (!data) {
- CGImageRef imageRef = self.CGImage ? (CGImageRef)CFRetain(self.CGImage) : nil;
- if (imageRef) {
- CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
- CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(imageRef) & kCGBitmapAlphaInfoMask;
- BOOL hasAlpha = NO;
- if (alphaInfo == kCGImageAlphaPremultipliedLast ||
- alphaInfo == kCGImageAlphaPremultipliedFirst ||
- alphaInfo == kCGImageAlphaLast ||
- alphaInfo == kCGImageAlphaFirst) {
- hasAlpha = YES;
- }
- if (self.imageOrientation != UIImageOrientationUp) {
- CGImageRef rotated = YYCGImageCreateCopyWithOrientation(imageRef, self.imageOrientation, bitmapInfo | alphaInfo);
- if (rotated) {
- CFRelease(imageRef);
- imageRef = rotated;
- }
- }
- @autoreleasepool {
- UIImage *newImage = [UIImage imageWithCGImage:imageRef];
- if (newImage) {
- if (hasAlpha) {
- data = UIImagePNGRepresentation([UIImage imageWithCGImage:imageRef]);
- } else {
- data = UIImageJPEGRepresentation([UIImage imageWithCGImage:imageRef], 0.9); // same as Apple's example
- }
- }
- }
- CFRelease(imageRef);
- }
- }
- if (!data) {
- data = UIImagePNGRepresentation(self);
- }
- return data;
- }
- @end
|