1 #include "rendertext.hpp"
2 #include "parseval.hpp"
3 #include "yuv4mpeg.hpp"
4 #include "yuvconvert.hpp"
13 static FT_Library ft_handle
;
15 void mix(uint16_t& a
, uint16_t b
, uint32_t t
)
17 a
= (a
* (65536 - t
) + b
* t
) >> 16;
20 void mix(uint8_t& a
, uint8_t b
, uint32_t t
)
22 a
= (a
* (256 - t
) + b
* t
) >> 8;
25 void render_halo(uint8_t* dest
, const uint8_t* src
, size_t width
, size_t height
, signed thickness
)
28 memcpy(dest
, src
, width
* height
);
29 for(unsigned j
= 0; j
< height
; j
++)
30 for(unsigned i
= 0; i
< width
; i
++)
31 if(dest
[j
* width
+ i
] == 1)
32 for(signed y
= -thickness
; y
<= thickness
; y
++) {
37 for(signed x
= -thickness
; x
<= thickness
; x
++) {
42 if(dest
[(j
+ y
) * width
+ (i
+ x
)] == 0)
43 dest
[(j
+ y
) * width
+ (i
+ x
)] = 2;
54 void throw_ft_error(FT_Error code
)
58 //There's no built-in error table in Freetype 2???
60 #define FT_ERRORDEF(e, v, s) {e, s},
61 #define FT_ERROR_START_LIST {
62 #define FT_ERROR_END_LIST {0, 0}};
63 static struct errmsg freetype_errors
[] =
65 struct errmsg
* i
= freetype_errors
;
68 throw std::runtime_error(std::string("Freetype2 error: ") + i
->message
);
72 s
<< "Freetype2 error: Unknown error #" << code
;
73 throw std::runtime_error(s
.str());
76 struct render_line_info
84 struct render_line_info
ft_rendered_size_line(FT_Face face
, const std::string
& text
)
86 struct render_line_info l
;
87 uint16_t state
= utf8_initial_state
;
88 size_t len
= text
.length();
90 l
.width
= l
.height
= l
.baseline
= l
.offset
= 0;
91 for(size_t i
= 0; i
<= len
; i
++) {
93 int32_t sym
= utf8_parse_byte((i
< len
) ? static_cast<uint8_t>(text
[i
]) : -1, state
);
97 unsigned gslot
= FT_Get_Char_Index(face
, sym
);
100 std::cerr
<< "Warning: Symbol " << sym
<< " not present in font!" << std::endl
;
104 throw_ft_error(FT_Load_Glyph(face
, gslot
, FT_LOAD_DEFAULT
));
105 throw_ft_error(FT_Render_Glyph(face
->glyph
, FT_RENDER_MODE_MONO
));
106 } catch(std::exception
& e
) {
107 std::cerr
<< "Warning: Symbol " << sym
<< " can't be rendered: " << e
.what()
111 //If offset needs adjustment, do that.
112 base
= basepos
+ face
->glyph
->bitmap_left
;
113 if(base
< 0 && l
.offset
< static_cast<size_t>(-base
))
114 l
.offset
= static_cast<size_t>(-base
);
115 //If baseline need adjustment, do that.
116 base
= -face
->glyph
->bitmap_top
;
117 if(base
< 0 && l
.baseline
< static_cast<size_t>(-base
))
118 l
.baseline
= static_cast<size_t>(-base
);
120 base
= basepos
+ face
->glyph
->bitmap_left
+ face
->glyph
->bitmap
.width
;
121 if(base
> static_cast<ssize_t
>(l
.width
))
124 base
= -face
->glyph
->bitmap_top
+ face
->glyph
->bitmap
.rows
;
125 if(base
> static_cast<ssize_t
>(l
.height
))
127 basepos
= basepos
+ (face
->glyph
->advance
.x
>> 6);
129 //Width / height don't take negative excursions into account.
130 l
.width
= l
.width
+ l
.offset
;
131 l
.height
= l
.height
+ l
.baseline
;
136 ssize_t
align_position(size_t space
, size_t size
, int32_t pos
)
138 ssize_t freespace
= space
- size
;
139 return ((pos
+ 1000) * freespace
) / 2000;
142 void ft_render_text_line(FT_Face face
, const std::string
& text
, std::vector
<uint8_t>& data
, size_t width
,
143 size_t height
, ssize_t alignment
, size_t y
, struct render_line_info l
)
145 ssize_t p
= align_position(width
, l
.width
, alignment
);
146 if(y
>= height
|| p
>= static_cast<ssize_t
>(width
))
147 return; //Nothing fits.
151 uint16_t state
= utf8_initial_state
;
152 size_t len
= text
.length();
153 //We need to compute the window offset.
154 offset_y
= l
.baseline
+ y
;
155 offset_x
= l
.offset
+ p
;
156 for(size_t i
= 0; i
<= len
; i
++) {
157 int32_t sym
= utf8_parse_byte((i
< len
) ? static_cast<uint8_t>(text
[i
]) : -1, state
);
160 //Got unicode symbol.
161 unsigned gslot
= FT_Get_Char_Index(face
, sym
);
164 std::cerr
<< "Warning: Symbol " << sym
<< " not present in font!" << std::endl
;
168 throw_ft_error(FT_Load_Glyph(face
, gslot
, FT_LOAD_DEFAULT
));
169 throw_ft_error(FT_Render_Glyph(face
->glyph
, FT_RENDER_MODE_MONO
));
170 } catch(std::exception
& e
) {
171 std::cerr
<< "Warning: Symbol " << sym
<< " can't be rendered: " << e
.what()
175 ssize_t render_x
= basepos
+ face
->glyph
->bitmap_left
+ offset_x
;
176 ssize_t render_y
= -face
->glyph
->bitmap_top
+ offset_y
;
177 for(int j
= 0; (unsigned)j
< face
->glyph
->bitmap
.rows
&& render_y
+ j
<
178 static_cast<ssize_t
>(height
); j
++) {
181 const uint8_t* ldata
= face
->glyph
->bitmap
.buffer
+ j
* face
->glyph
->bitmap
.pitch
;
182 uint8_t* tmpr
= &data
[(render_y
+ j
) * width
];
183 for(int k
= 0; (unsigned)k
< face
->glyph
->bitmap
.width
&& render_x
+ k
<
184 static_cast<ssize_t
>(width
); k
++) {
187 tmpr
[render_x
+ k
] = ((ldata
[k
>> 3] >> (7 - (k
& 7))) & 1) ? 1 : 0;
190 basepos
= basepos
+ (face
->glyph
->advance
.x
>> 6);
194 void ft_render_text(FT_Face face
, const std::string
& text
, std::vector
<uint8_t>& data
, size_t width
,
195 size_t height
, ssize_t alignment
, size_t spacing
, size_t y
= 0)
197 std::string _text
= text
;
198 //If the string contains linefeeds, break it up.
199 size_t p
= _text
.find("\\n");
200 if(p
< _text
.length()) {
201 std::string t1
= _text
.substr(0, p
);
202 std::string t2
= _text
.substr(p
+ 2);
203 auto g1
= ft_rendered_size_line(face
, t1
);
204 ft_render_text_line(face
, t1
, data
, width
, height
, alignment
, y
, g1
);
205 ft_render_text(face
, t2
, data
, width
, height
, alignment
, spacing
, y
+ g1
.height
+ spacing
);
208 //The text is in one piece if this place is reached.
209 auto g1
= ft_rendered_size_line(face
, text
);
210 ft_render_text_line(face
, text
, data
, width
, height
, alignment
, y
, g1
);
213 std::pair
<size_t, size_t> ft_rendered_size(FT_Face face
, const std::string
& text
, size_t spacing
)
215 std::string _text
= text
;
216 //If the string contains linefeeds, break it up.
217 size_t p
= _text
.find("\\n");
218 if(p
< _text
.length()) {
219 std::string t1
= _text
.substr(0, p
);
220 std::string t2
= _text
.substr(p
+ 2);
221 auto g1
= ft_rendered_size_line(face
, t1
);
222 auto g2
= ft_rendered_size(face
, t2
, spacing
);
224 w
= (g1
.width
> g2
.first
) ? g1
.width
: g2
.first
;
225 h
= g1
.height
+ spacing
+ g2
.second
;
226 return std::make_pair(w
, h
);
228 //The text is in one piece if this place is reached.
229 auto g
= ft_rendered_size_line(face
, text
);
230 return std::make_pair(g
.width
, g
.height
);
233 std::string
read_subtitle_from_file(std::string filename
)
237 std::ifstream
x(filename
);
239 throw std::runtime_error("Can't open '" + filename
+ "'");
240 while(std::getline(x
, tmp
)) {
242 content
= content
+ "\n" + tmp
;
246 std::ostringstream content2
;
247 for(size_t i
= 0; i
< content
.length(); i
++)
248 if(content
[i
] == '\n')
250 else if(content
[i
] == '\\')
253 content2
<< content
[i
];
254 return content2
.str();
257 ssize_t
position_subtitle(size_t space
, size_t size
, int32_t pos
, bool absolute
)
262 ssize_t freespace
= space
- size
;
263 return ((pos
+ 1000) * freespace
) / 2000;
267 yuvc_converter
* get_converter(const regex_results
& r
)
269 std::string csp
= "rec601";
272 yuvc_converter
* c
= yuvc_get_converter(RGB_RGB
, csp
);
276 std::cerr
<< "Warning: Stream YUV variant unspecified, assuming rec601." << std::endl
;
278 std::cerr
<< "Warning: Stream YUV variant unsupported, falling back to rec601." << std::endl
;
279 return yuvc_get_converter(RGB_RGB
, "rec601");
282 void stamp_pos(size_t space
, size_t dimension
, ssize_t pos
, size_t& fpos
, size_t& spos
, size_t& sdim
)
301 else if(fpos
+ sdim
> space
)
306 std::pair
<size_t, size_t> text_render_parameters::render_size(const std::string
& text
)
310 //Init freetype2 if needed.
312 throw_ft_error(FT_Init_FreeType(&ft_handle
));
315 //Load the font face.
317 throw std::runtime_error("Font file needed for text rendering");
318 throw_ft_error(FT_New_Face(ft_handle
, fontfile
.c_str(), fontface
, &face
));
319 throw_ft_error(FT_Set_Pixel_Sizes(face
, 0, fontsize
));
321 auto g
= ft_rendered_size(face
, text
, text_spacing
);
322 return std::make_pair(g
.first
+ 2 * halo_thickness
, g
.second
+ 2 * halo_thickness
);
325 parsed_png
& text_render_parameters::render(const std::string
& text
)
328 std::vector
<uint8_t> tmp
;
329 size_t width
, height
;
330 size_t width2
, height2
;
332 //Init freetype2 if needed.
334 throw_ft_error(FT_Init_FreeType(&ft_handle
));
337 //Load the font face.
339 throw std::runtime_error("Font file needed for text rendering");
340 throw_ft_error(FT_New_Face(ft_handle
, fontfile
.c_str(), fontface
, &face
));
341 throw_ft_error(FT_Set_Pixel_Sizes(face
, 0, fontsize
));
343 //First we need to figure out the size of the rendered bitmap. Then render the actual bitmap.
344 auto g
= ft_rendered_size(face
, text
, text_spacing
);
347 width2
= width
+ 2 * halo_thickness
;
348 height2
= height
+ 2 * halo_thickness
;
349 tmp
.resize(width2
* height2
);
350 memset(&tmp
[0], 0, width2
* height2
);
351 ft_render_text(face
, text
, tmp
, width
, height
, text_alignment
, text_spacing
);
353 //Do in-place padding by halo_thickness on all sides.
354 if(halo_thickness
> 0) {
355 for(size_t i
= height
- 1; i
< height
; i
--)
356 memmove(&tmp
[width2
* (i
+ halo_thickness
) + halo_thickness
], &tmp
[width
* i
], width
);
357 for(size_t i
= 0; i
< height
; i
++) {
358 memset(&tmp
[width2
* (i
+ halo_thickness
)], 0, halo_thickness
);
359 memset(&tmp
[width2
* (i
+ halo_thickness
+ 1) - halo_thickness
], 0, halo_thickness
);
361 memset(&tmp
[0], 0, width2
* halo_thickness
);
362 render_halo(&tmp
[0], &tmp
[0], width2
, height2
, halo_thickness
);
365 parsed_png
& png
= *new parsed_png(width2
, height2
);
366 for(size_t i
= 0; i
< width2
* height2
; i
++) {
367 if(tmp
[i
] == 0) png
.data
[i
] = bg_color
;
368 if(tmp
[i
] == 1) png
.data
[i
] = fg_color
;
369 if(tmp
[i
] == 2) png
.data
[i
] = halo_color
;
374 text_render_parameters::text_render_parameters()
380 fg_color
= 0xFFFFFFFFU
;
381 halo_color
= 0xFF000000U
;
386 bool text_render_parameters::argument(const std::string
& arg
)
389 if(r
= regex("--font-file=(.*)", arg
)) {
392 } else if(r
= regex("--font-size=(.*)", arg
)) {
393 fontsize
= parse_value
<unsigned>(r
[1]);
395 } else if(r
= regex("--text-alignment=left", arg
)) {
396 text_alignment
= -1000;
398 } else if(r
= regex("--text-alignment=center", arg
)) {
401 } else if(r
= regex("--text-alignment=right", arg
)) {
402 text_alignment
= 1000;
404 } else if(r
= regex("--text-alignment=(.*)", arg
)) {
405 text_alignment
= parse_value
<signed>(r
[1]);
407 } else if(r
= regex("--text-spacing=(.*)", arg
)) {
408 text_spacing
= parse_value
<signed>(r
[1]);
410 } else if(r
= regex("--font-face=(.*)", arg
)) {
411 fontface
= parse_value
<long>(r
[1]);
413 } else if(r
= regex("--fg-color=(.*),(.*),(.*),(.*)", arg
)) {
414 unsigned R
= parse_value
<unsigned>(r
[1]);
415 unsigned g
= parse_value
<unsigned>(r
[2]);
416 unsigned b
= parse_value
<unsigned>(r
[3]);
417 unsigned a
= parse_value
<unsigned>(r
[4]);
418 fg_color
= (a
<< 24) | (b
<< 16) | (g
<< 8) | R
;
420 } else if(r
= regex("--fg-color=(.*),(.*),(.*)", arg
)) {
421 unsigned R
= parse_value
<unsigned>(r
[1]);
422 unsigned g
= parse_value
<unsigned>(r
[2]);
423 unsigned b
= parse_value
<unsigned>(r
[3]);
424 fg_color
= (fg_color
& 0xFF000000U
) | (b
<< 16) | (g
<< 8) | R
;
426 } else if(r
= regex("--fg-alpha=(.*)", arg
)) {
427 unsigned a
= parse_value
<unsigned>(r
[1]);
428 fg_color
= (fg_color
& 0xFFFFFFU
) | (a
<< 24);
430 } else if(r
= regex("--bg-color=(.*),(.*),(.*),(.*)", arg
)) {
431 unsigned R
= parse_value
<unsigned>(r
[1]);
432 unsigned g
= parse_value
<unsigned>(r
[2]);
433 unsigned b
= parse_value
<unsigned>(r
[3]);
434 unsigned a
= parse_value
<unsigned>(r
[4]);
435 bg_color
= (a
<< 24) | (b
<< 16) | (g
<< 8) | R
;
437 } else if(r
= regex("--bg-color=(.*),(.*),(.*)", arg
)) {
438 unsigned R
= parse_value
<unsigned>(r
[1]);
439 unsigned g
= parse_value
<unsigned>(r
[2]);
440 unsigned b
= parse_value
<unsigned>(r
[3]);
441 bg_color
= (bg_color
& 0xFF000000U
) | (b
<< 16) | (g
<< 8) | R
;
443 } else if(r
= regex("--bg-alpha=(.*)", arg
)) {
444 unsigned a
= parse_value
<unsigned>(r
[1]);
445 bg_color
= (bg_color
& 0xFFFFFFU
) | (a
<< 24);
447 } else if(r
= regex("--halo-color=(.*),(.*),(.*),(.*)", arg
)) {
448 unsigned R
= parse_value
<unsigned>(r
[1]);
449 unsigned g
= parse_value
<unsigned>(r
[2]);
450 unsigned b
= parse_value
<unsigned>(r
[3]);
451 unsigned a
= parse_value
<unsigned>(r
[4]);
452 halo_color
= (a
<< 24) | (b
<< 16) | (g
<< 8) | R
;
454 } else if(r
= regex("--halo-color=(.*),(.*),(.*)", arg
)) {
455 unsigned R
= parse_value
<unsigned>(r
[1]);
456 unsigned g
= parse_value
<unsigned>(r
[2]);
457 unsigned b
= parse_value
<unsigned>(r
[3]);
458 halo_color
= (halo_color
& 0xFF000000U
) | (b
<< 16) | (g
<< 8) | R
;
460 } else if(r
= regex("--halo-alpha=(.*)", arg
)) {
461 unsigned a
= parse_value
<unsigned>(r
[1]);
462 halo_color
= (halo_color
& 0xFFFFFFU
) | (a
<< 24);
464 } else if(r
= regex("--halo-thickness=(.*)", arg
)) {
465 halo_thickness
= parse_value
<unsigned>(r
[1]);
466 if(halo_thickness
< 0)
473 pre_subtitle::pre_subtitle()
478 pre_subtitle::~pre_subtitle()
483 subtitle::subtitle(const pre_subtitle
& presub
, const yuv4mpeg_stream_header
& strmh
)
486 if(strmh
.fps_n
&& strmh
.fps_d
)
487 fps
= 1.0 * strmh
.fps_n
/ strmh
.fps_d
;
488 frame
= presub
.start
.get_frame(fps
, 0);
489 duration
= presub
.duration
.get_frame(fps
, 0);
490 width
= presub
.image
->width
;
491 height
= presub
.image
->height
;
492 x
= position_subtitle(strmh
.width
, width
, presub
.xpos
, presub
.xabsolute
);
493 y
= position_subtitle(strmh
.height
, height
, presub
.ypos
, presub
.yabsolute
);
494 if(x
< 0 || y
< 0 || x
+ width
> strmh
.width
|| y
+ height
> strmh
.height
)
495 std::cerr
<< "Warning: Subtitle is partially offscreen" << std::endl
;
497 if(strmh
.chroma
== "rgb") {
499 data
= new uint8_t[width
* height
* 3];
501 planesep
= width
* height
* 3;
502 for(size_t i
= 0; i
< width
* height
; i
++) {
503 data
[3 * i
+ 0] = presub
.image
->data
[i
];
504 data
[3 * i
+ 1] = presub
.image
->data
[i
] >> 8;
505 data
[3 * i
+ 2] = presub
.image
->data
[i
] >> 16;
507 } else if(strmh
.chroma
== "444" || strmh
.chroma
== "444p16") {
508 std::vector
<uint8_t> tmp
;
509 bits16
= (strmh
.chroma
== "444p16");
510 tmp
.resize(width
* height
* 3);
511 data
= new uint8_t[width
* height
* (bits16
? 6 : 3)];
512 uint16_t* data16
= reinterpret_cast<uint16_t*>(data
);
514 planesep
= width
* height
;
515 yuvc_converter
* conv
= get_converter(strmh
.find_extension("yuvmatrix=(.*)"));
516 for(size_t i
= 0; i
< width
* height
; i
++) {
517 tmp
[3 * i
+ 0] = presub
.image
->data
[i
];
518 tmp
[3 * i
+ 1] = presub
.image
->data
[i
] >> 8;
519 tmp
[3 * i
+ 2] = presub
.image
->data
[i
] >> 16;
522 conv
->transform(data16
, &tmp
[0], width
* height
);
524 conv
->transform(data
, &tmp
[0], width
* height
);
526 (stringfmt() << "Chroma type " << strmh
.chroma
<< " not supported for subtitling").throwex();
527 alpha
= new uint32_t[width
* height
];
529 for(size_t i
= 0; i
< width
* height
; i
++)
530 alpha
[i
] = ((presub
.image
->data
[i
] >> 24) + (presub
.image
->data
[i
] >> 31)) << 8;
532 for(size_t i
= 0; i
< width
* height
; i
++)
533 alpha
[i
] = (presub
.image
->data
[i
] >> 24) + (presub
.image
->data
[i
] >> 31);
536 subtitle::~subtitle()
543 subtitle_render_context::subtitle_render_context()
549 duration
= timemarker("5.0");
552 bool subtitle_render_context::argument(const std::string
& arg
, pre_subtitle
*& presub
)
555 if(tparams
.argument(arg
))
557 if(r
= regex("--(text|file|png)=([^,]*),(.*)", arg
)) {
558 std::string text
= r
[3];
559 timemarker
start(r
[2]);
562 text
= read_subtitle_from_file(text
);
564 png
= new parsed_png(text
);
566 png
= &tparams
.render(text
);
567 presub
= new pre_subtitle
;
571 presub
->xabsolute
= xabsolute
;
572 presub
->yabsolute
= yabsolute
;
573 presub
->start
= start
;
574 presub
->duration
= duration
;
576 } else if(r
= regex("--duration=(.*)", arg
)) {
577 duration
= timemarker(r
[1]);
579 } else if(r
= regex("--xpos=left", arg
)) {
583 } else if(r
= regex("--xpos=center", arg
)) {
587 } else if(r
= regex("--xpos=right", arg
)) {
591 } else if(r
= regex("--xpos=abs:([+-]?[0-9]+)", arg
)) {
593 xpos
= parse_value
<ssize_t
>(r
[1]);
595 } else if(r
= regex("--xpos=([+-]?[0-9]+)", arg
)) {
597 xpos
= parse_value
<ssize_t
>(r
[1]);
599 } else if(r
= regex("--ypos=top", arg
)) {
603 } else if(r
= regex("--ypos=center", arg
)) {
607 } else if(r
= regex("--ypos=bottom", arg
)) {
611 } else if(r
= regex("--ypos=abs:([+-]?[0-9]+)", arg
)) {
613 ypos
= parse_value
<ssize_t
>(r
[1]);
615 } else if(r
= regex("--ypos=([+-]?[0-9]+)", arg
)) {
617 ypos
= parse_value
<ssize_t
>(r
[1]);
624 std::vector
<subtitle
*> subtitle::from_presub(const std::vector
<pre_subtitle
*>& presubs
,
625 const yuv4mpeg_stream_header
& strmh
)
627 std::vector
<subtitle
*> ret
;
628 for(auto i
: presubs
) {
630 ret
.push_back(new subtitle(*i
, strmh
));
639 void subtitle::stamp(uint8_t* idata
, size_t iwidth
, size_t iheight
, uint64_t frameno
) const
641 if(frameno
< frame
|| frameno
> frame
+ duration
)
643 size_t fpos_x
, spos_x
, swidth
;
644 size_t fpos_y
, spos_y
, sheight
;
645 stamp_pos(iwidth
, width
, x
, fpos_x
, spos_x
, swidth
);
646 stamp_pos(iheight
, height
, y
, fpos_y
, spos_y
, sheight
);
647 if(!swidth
|| !sheight
)
649 if(planes
== 3 && bits16
) {
650 size_t iplanesep
= iwidth
* iheight
;
651 uint16_t* idata2
= reinterpret_cast<uint16_t*>(idata
);
652 const uint16_t* data2
= reinterpret_cast<const uint16_t*>(data
);
653 for(size_t i
= 0; i
< sheight
; i
++)
654 for(size_t j
= 0; j
< swidth
; j
++) {
655 size_t ioffset
= (fpos_y
+ i
) * iwidth
+ (fpos_x
+ j
);
656 size_t offset
= (spos_y
+ i
) * width
+ (spos_x
+ j
);
657 mix(idata2
[ioffset
], data2
[offset
], alpha
[offset
]);
658 mix(idata2
[ioffset
+ iplanesep
], data2
[offset
+ planesep
], alpha
[offset
]);
659 mix(idata2
[ioffset
+ iplanesep
], data2
[offset
+ planesep
], alpha
[offset
]);
661 } else if(planes
== 3 && !bits16
) {
662 size_t iplanesep
= iwidth
* iheight
;
663 for(size_t i
= 0; i
< sheight
; i
++)
664 for(size_t j
= 0; j
< swidth
; j
++) {
665 size_t ioffset
= (fpos_y
+ i
) * iwidth
+ (fpos_x
+ j
);
666 size_t offset
= (spos_y
+ i
) * width
+ (spos_x
+ j
);
667 mix(idata
[ioffset
], data
[offset
], alpha
[offset
]);
668 mix(idata
[ioffset
+ iplanesep
], data
[offset
+ planesep
], alpha
[offset
]);
669 mix(idata
[ioffset
+ 2 * iplanesep
], data
[offset
+ 2 * planesep
], alpha
[offset
]);
671 } else if(planes
== 1) {
672 for(size_t i
= 0; i
< sheight
; i
++)
673 for(size_t j
= 0; j
< swidth
; j
++) {
674 size_t ioffset
= (fpos_y
+ i
) * 3 * iwidth
+ 3 * (fpos_x
+ j
);
675 size_t offset2
= (spos_y
+ i
) * width
+ (spos_x
+ j
);
676 size_t offset
= offset2
* 3;
677 mix(idata
[ioffset
], data
[offset
], alpha
[offset2
]);
678 mix(idata
[ioffset
+ 1], data
[offset
+ 1], alpha
[offset2
]);
679 mix(idata
[ioffset
+ 2], data
[offset
+ 2], alpha
[offset2
]);