Skip to content

Commit b46c7d6

Browse files
committed
Write the libbpgenc base on the original command line tool. Support static BPG encoding and animated BPG encoding.
Current BPG encoding based on the x265 encoder, jctvc is not supported
1 parent df396b3 commit b46c7d6

File tree

17 files changed

+4209
-13
lines changed

17 files changed

+4209
-13
lines changed

Example/Podfile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ platform :ios, '8.0'
22
use_frameworks!
33

44
target 'SDWebImageBPGCoder_Example' do
5-
pod 'SDWebImageBPGCoder', :path => '../'
5+
pod 'SDWebImageBPGCoder', :path => '../', :subspecs => [
6+
'libbpg',
7+
'bpgenc'
8+
]
69

710
target 'SDWebImageBPGCoder_Tests' do
811
inherit! :search_paths

Example/Podfile.lock

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
PODS:
22
- Expecta (1.0.6)
33
- SDWebImage/Core (5.0.0-beta4)
4-
- SDWebImageBPGCoder (0.3.0):
4+
- SDWebImageBPGCoder/bpgenc (0.3.0):
5+
- SDWebImage/Core (>= 5.0.0-beta4)
6+
- SDWebImageBPGCoder/libbpg
7+
- SDWebImageBPGCoder/libbpg (0.3.0):
58
- SDWebImage/Core (>= 5.0.0-beta4)
69
- Specta (1.0.7)
710

811
DEPENDENCIES:
912
- Expecta
10-
- SDWebImageBPGCoder (from `../`)
13+
- SDWebImageBPGCoder/bpgenc (from `../`)
14+
- SDWebImageBPGCoder/libbpg (from `../`)
1115
- Specta
1216

1317
SPEC REPOS:
@@ -23,9 +27,9 @@ EXTERNAL SOURCES:
2327
SPEC CHECKSUMS:
2428
Expecta: 3b6bd90a64b9a1dcb0b70aa0e10a7f8f631667d5
2529
SDWebImage: 56bfe1114b11f1d5766d6d0e3b3456cfd7e1e8b7
26-
SDWebImageBPGCoder: c35c3aa83b6dd86a63e3fb013a29a2db6b4a77c3
30+
SDWebImageBPGCoder: a3ddd51d37aeed8116f272d280441e03af4c171a
2731
Specta: 3e1bd89c3517421982dc4d1c992503e48bd5fe66
2832

29-
PODFILE CHECKSUM: 2175744386f7a924ca415e90b59e6cf33d634c7f
33+
PODFILE CHECKSUM: 0f81121cdc589dfdeb358147154e6bd7e7bcbd4f
3034

3135
COCOAPODS: 1.5.3
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>BuildSystemType</key>
6+
<string>Original</string>
7+
</dict>
8+
</plist>

Example/SDWebImageBPGCoder/SDViewController.m

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,23 @@ - (void)viewDidLoad
3636
[imageView1 sd_setImageWithURL:staticBPGURL completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
3737
if (image) {
3838
NSLog(@"Static BPG load success");
39+
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
40+
NSData *bpgData = [SDImageBPGCoder.sharedCoder encodedDataWithImage:image format:SDImageFormatBPG options:nil];
41+
if (bpgData) {
42+
NSLog(@"Static BPG encode success");
43+
}
44+
});
3945
}
4046
}];
4147
[imageView2 sd_setImageWithURL:animatedBPGURL completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
4248
if (image.images) {
4349
NSLog(@"Animated BPG load success");
50+
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
51+
NSData *bpgData = [SDImageBPGCoder.sharedCoder encodedDataWithImage:image format:SDImageFormatBPG options:nil];
52+
if (bpgData) {
53+
NSLog(@"Animated BPG encode success");
54+
}
55+
});
4456
}
4557
}];
4658

SDWebImageBPGCoder.podspec

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,32 @@ TODO: Add long description of the pod here.
3131
s.tvos.deployment_target = '9.0'
3232
s.watchos.deployment_target = '2.0'
3333

34-
s.source_files = 'SDWebImageBPGCoder/Classes/**/*', 'Vendor/libbpg/include/libbpg.h', 'SDWebImageBPGCoder/Module/SDWebImageBPGCoder.h'
34+
s.default_subspecs = 'libbpg'
3535
s.module_map = 'SDWebImageBPGCoder/Module/SDWebImageBPGCoder.modulemap'
36-
s.public_header_files = 'SDWebImageBPGCoder/Classes/**/*.h', 'SDWebImageBPGCoder/Module/SDWebImageBPGCoder.h'
37-
s.osx.vendored_libraries = 'Vendor/libbpg/lib/mac/libbpg.a'
38-
s.ios.vendored_libraries = 'Vendor/libbpg/lib/ios/libbpg.a'
39-
s.tvos.vendored_libraries = 'Vendor/libbpg/lib/tvos/libbpg.a'
40-
s.watchos.vendored_libraries = 'Vendor/libbpg/lib/watchos/libbpg.a'
36+
37+
s.subspec 'libbpg' do |ss|
38+
ss.source_files = 'SDWebImageBPGCoder/Classes/SDImageBPGCoder.{h,m}', 'SDWebImageBPGCoder/Module/SDWebImageBPGCoder.h', 'Vendor/libbpg/include/libbpg.h'
39+
ss.public_header_files = 'SDWebImageBPGCoder/Classes/SDImageBPGCoder.h', 'SDWebImageBPGCoder/Module/SDWebImageBPGCoder.h', 'Vendor/libbpg/include/libbpg.h'
40+
ss.osx.vendored_libraries = 'Vendor/libbpg/lib/mac/libbpg.a'
41+
ss.ios.vendored_libraries = 'Vendor/libbpg/lib/ios/libbpg.a'
42+
ss.tvos.vendored_libraries = 'Vendor/libbpg/lib/tvos/libbpg.a'
43+
ss.watchos.vendored_libraries = 'Vendor/libbpg/lib/watchos/libbpg.a'
44+
end
45+
46+
s.subspec 'bpgenc' do |ss|
47+
ss.dependency 'SDWebImageBPGCoder/libbpg'
48+
ss.source_files = 'SDWebImageBPGCoder/Classes/bpgenc/*', 'Vendor/libx265/include/x265.h', 'Vendor/libx265/include/x265_config.h'
49+
ss.public_header_files = 'SDWebImageBPGCoder/Classes/bpgenc/*.h'
50+
ss.xcconfig = {
51+
'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) USE_X265=1',
52+
'WARNING_CFLAGS' => '$(inherited) -Wno-shorten-64-to-32 -Wno-conditional-uninitialized -Wno-unused-variable'
53+
}
54+
ss.osx.vendored_libraries = 'Vendor/libx265/lib/mac/libx265.a'
55+
ss.ios.vendored_libraries = 'Vendor/libx265/lib/ios/libx265.a'
56+
ss.tvos.vendored_libraries = 'Vendor/libx265/lib/tvos/libx265.a'
57+
ss.watchos.vendored_libraries = 'Vendor/libx265/lib/watchos/libx265.a'
58+
ss.libraries = 'c++'
59+
end
60+
4161
s.dependency 'SDWebImage/Core', '>= 5.0.0-beta4'
4262
end

SDWebImageBPGCoder/Classes/SDImageBPGCoder.m

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,53 @@
1212
#import "libbpg.h"
1313
#endif
1414

15+
#if defined(USE_X265)
16+
#import "bpgenc.h"
17+
#import <Accelerate/Accelerate.h>
18+
#endif
19+
1520
#define SD_FOUR_CC(c1,c2,c3,c4) ((uint32_t)(((c4) << 24) | ((c3) << 16) | ((c2) << 8) | (c1)))
1621

1722
static void FreeImageData(void *info, const void *data, size_t size) {
1823
free((void *)data);
1924
}
2025

26+
#if defined(USE_X265)
27+
static int WriteImageData(void *opaque, const uint8_t *buf, int buf_len) {
28+
NSMutableData *imageData = (__bridge NSMutableData *)opaque;
29+
NSCParameterAssert(imageData);
30+
NSCParameterAssert(buf);
31+
32+
[imageData appendBytes:buf length:buf_len];
33+
return buf_len;
34+
}
35+
36+
static void FillRGBABufferWithBPGImage(vImage_Buffer *red, vImage_Buffer *green, vImage_Buffer *blue, vImage_Buffer *alpha, BPGImage *img) {
37+
// libbpg RGB coder format order is GBR/GBRA
38+
red->width = img->w;
39+
red->height = img->h;
40+
red->data = img->data[2];
41+
red->rowBytes = img->linesize[2];
42+
43+
green->width = img->w;
44+
green->height = img->h;
45+
green->data = img->data[0];
46+
green->rowBytes = img->linesize[0];
47+
48+
blue->width = img->w;
49+
blue->height = img->h;
50+
blue->data = img->data[1];
51+
blue->rowBytes = img->linesize[1];
52+
53+
if (img->has_alpha) {
54+
alpha->width = img->w;
55+
alpha->height = img->h;
56+
alpha->data = img->data[3];
57+
alpha->rowBytes = img->linesize[3];
58+
}
59+
}
60+
#endif
61+
2162
@implementation SDImageBPGCoder
2263

2364
+ (instancetype)sharedCoder {
@@ -128,12 +169,198 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(SDImageCoderOptions *)
128169

129170
#pragma mark - Encode
130171
- (BOOL)canEncodeToFormat:(SDImageFormat)format {
172+
if (format == SDImageFormatBPG) {
173+
#if defined(USE_X265)
174+
return YES;
175+
#else
176+
return NO;
177+
#endif
178+
}
131179
return NO;
132180
}
133181

134182
- (NSData *)encodedDataWithImage:(UIImage *)image format:(SDImageFormat)format options:(SDImageCoderOptions *)options {
183+
#if defined(USE_X265)
184+
double compressionQuality = 1;
185+
if (options[SDImageCoderEncodeCompressionQuality]) {
186+
compressionQuality = [options[SDImageCoderEncodeCompressionQuality] doubleValue];
187+
}
188+
BOOL encodeFirstFrame = [options[SDImageCoderEncodeFirstFrameOnly] boolValue];
189+
190+
return [self sd_encodedBPGDataWithImage:image quality:compressionQuality encodeFirstFrame:encodeFirstFrame];
191+
#else
135192
return nil;
193+
#endif
194+
}
195+
196+
#if defined(USE_X265)
197+
- (nullable NSData *)sd_encodedBPGDataWithImage:(nonnull UIImage *)image quality:(double)quality encodeFirstFrame:(BOOL)encodeFirstFrame {
198+
BPGEncoderContext *enc_ctx;
199+
BPGEncoderParameters *p;
200+
p = bpg_encoder_param_alloc();
201+
if (!p) {
202+
return nil;
203+
}
204+
// BPG quality is from [0-51], 0 means the best quality but the biggest size. But we define 1.0 the best qualiy, 0.0 the smallest size.
205+
if (quality == 1) {
206+
p->lossless = 1;
207+
}
208+
p->qp = (1 - quality) * 51;
209+
210+
NSArray<SDImageFrame *> *frames = [SDImageCoderHelper framesFromAnimatedImage:image];
211+
NSMutableData *mutableData = [NSMutableData data];
212+
213+
if (encodeFirstFrame || frames.count == 0) {
214+
// for static BPG image
215+
enc_ctx = bpg_encoder_open(p);
216+
if (!enc_ctx) {
217+
bpg_encoder_param_free(p);
218+
return nil;
219+
}
220+
221+
BPGImage *img = [self sd_encodedBPGFrameWithImage:image];
222+
if (!img) {
223+
bpg_encoder_close(enc_ctx);
224+
bpg_encoder_param_free(p);
225+
return nil;
226+
}
227+
bpg_encoder_encode(enc_ctx, img, WriteImageData, (__bridge void *)mutableData);
228+
bpg_image_free(img);
229+
} else {
230+
// for aniated BPG image
231+
p->animated = 1;
232+
233+
enc_ctx = bpg_encoder_open(p);
234+
if (!enc_ctx) {
235+
bpg_encoder_param_free(p);
236+
return nil;
237+
}
238+
239+
for (size_t i = 0; i < frames.count; i++) {
240+
@autoreleasepool {
241+
SDImageFrame *currentFrame = frames[i];
242+
243+
BPGImage *img = [self sd_encodedBPGFrameWithImage:currentFrame.image];
244+
if (!img) {
245+
bpg_encoder_close(enc_ctx);
246+
bpg_encoder_param_free(p);
247+
return nil;
248+
}
249+
// libbpg calculate the frame duration like this: seconds = frame_delay_num / frame_delay_den * frame_ticks
250+
int frame_ticks = currentFrame.duration * p->frame_delay_den / p->frame_delay_num;
251+
bpg_encoder_set_frame_duration(enc_ctx, frame_ticks);
252+
bpg_encoder_encode(enc_ctx, img, WriteImageData, (__bridge void *)mutableData);
253+
bpg_image_free(img);
254+
}
255+
}
256+
// When encoding animations, img = NULL indicates the end of the stream
257+
bpg_encoder_encode(enc_ctx, NULL, WriteImageData, (__bridge void *)mutableData);
258+
}
259+
260+
bpg_encoder_close(enc_ctx);
261+
bpg_encoder_param_free(p);
262+
263+
264+
return [mutableData copy];
265+
}
266+
267+
268+
- (BPGImage *)sd_encodedBPGFrameWithImage:(nonnull UIImage *)image {
269+
CGImageRef imageRef = image.CGImage;
270+
if (!imageRef) {
271+
return nil;
272+
}
273+
274+
BPGImageFormatEnum format = BPG_FORMAT_444;
275+
BPGColorSpaceEnum color_space = BPG_CS_RGB;
276+
277+
size_t width = CGImageGetWidth(imageRef);
278+
size_t height = CGImageGetHeight(imageRef);
279+
size_t bytesPerRow = CGImageGetBytesPerRow(imageRef);
280+
size_t bitsPerPixel = CGImageGetBitsPerPixel(imageRef);
281+
size_t bitsPerComponent = CGImageGetBitsPerComponent(imageRef);
282+
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
283+
CGImageAlphaInfo alphaInfo = bitmapInfo & kCGBitmapAlphaInfoMask;
284+
CGBitmapInfo byteOrderInfo = bitmapInfo & kCGBitmapByteOrderMask;
285+
BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone ||
286+
alphaInfo == kCGImageAlphaNoneSkipFirst ||
287+
alphaInfo == kCGImageAlphaNoneSkipLast);
288+
BOOL byteOrderNormal = NO;
289+
switch (byteOrderInfo) {
290+
case kCGBitmapByteOrderDefault: {
291+
byteOrderNormal = YES;
292+
} break;
293+
case kCGBitmapByteOrder32Little: {
294+
} break;
295+
case kCGBitmapByteOrder32Big: {
296+
byteOrderNormal = YES;
297+
} break;
298+
default: break;
299+
}
300+
301+
// libbpg supports 4 Planr16 channel for RGBA (BPGImage->data[4], which actually store uint16_t not uint8_t)
302+
// Actually, we can optimize to use RGB888 and convert into Planr16. However, since vImage is fast on GPU, use the built-in method (vImageConvert_RGB16UtoPlanar16U) can boost encoding time.
303+
vImageConverterRef convertor = NULL;
304+
vImage_Error v_error = kvImageNoError;
305+
306+
vImage_CGImageFormat srcFormat = {
307+
.bitsPerComponent = (uint32_t)bitsPerComponent,
308+
.bitsPerPixel = (uint32_t)bitsPerPixel,
309+
.colorSpace = CGImageGetColorSpace(imageRef),
310+
.bitmapInfo = bitmapInfo
311+
};
312+
vImage_CGImageFormat destFormat = {
313+
.bitsPerComponent = 16,
314+
.bitsPerPixel = hasAlpha ? 64 : 48,
315+
.colorSpace = [SDImageCoderHelper colorSpaceGetDeviceRGB],
316+
.bitmapInfo = hasAlpha ? kCGImageAlphaFirst | kCGBitmapByteOrderDefault : kCGImageAlphaNone | kCGBitmapByteOrderDefault // RGB16U/ARGB16U (Non-premultiplied to works for libbpg)
317+
};
318+
319+
convertor = vImageConverter_CreateWithCGImageFormat(&srcFormat, &destFormat, NULL, kvImageNoFlags, &v_error);
320+
if (v_error != kvImageNoError) {
321+
return nil;
322+
}
323+
324+
vImage_Buffer src;
325+
v_error = vImageBuffer_InitWithCGImage(&src, &srcFormat, NULL, imageRef, kvImageNoFlags);
326+
if (v_error != kvImageNoError) {
327+
return nil;
328+
}
329+
size_t destBytesPerRow = width * destFormat.bitsPerPixel / 8;
330+
vImage_Buffer dest;
331+
vImageBuffer_Init(&dest, height, width, hasAlpha ? 64 : 48, kvImageNoFlags);
332+
if (!dest.data) {
333+
free(src.data);
334+
return nil;
335+
}
336+
337+
// Convert input color mode to RGB16U/ARGB16U
338+
v_error = vImageConvert_AnyToAny(convertor, &src, &dest, NULL, kvImageNoFlags);
339+
free(src.data);
340+
vImageConverter_Release(convertor);
341+
if (v_error != kvImageNoError) {
342+
free(dest.data);
343+
return nil;
344+
}
345+
346+
BPGImage *img = bpg_image_alloc((int)width, (int)height, format, hasAlpha, color_space, (int)bitsPerComponent);
347+
348+
vImage_Buffer red, green, blue, alpha;
349+
FillRGBABufferWithBPGImage(&red, &green, &blue, &alpha, img);
350+
351+
if (hasAlpha) {
352+
v_error = vImageConvert_ARGB16UtoPlanar16U(&dest, &alpha, &red, &green, &blue, kvImageNoFlags);
353+
} else {
354+
v_error = vImageConvert_RGB16UtoPlanar16U(&dest, &red, &green, &blue, kvImageNoFlags);
355+
}
356+
free(dest.data);
357+
if (v_error != kvImageNoError) {
358+
return nil;
359+
}
360+
361+
return img;
136362
}
363+
#endif
137364

138365
// libbpg currently does not fully support progressive decoding (alpha-only and contains issue), this does not be compatible with `SDProgressiveImageCoder` protocol
139366

0 commit comments

Comments
 (0)