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
+ (ssize_t
)face
->glyph
->bitmap
.width
;
121 if(base
> static_cast<ssize_t
>(l
.width
))
124 base
= -face
->glyph
->bitmap_top
+ (ssize_t
)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
;
132 if(l
.height
> 1000000000 || l
.width
> 1000000000) {
133 std::cerr
<< "Subtitle: Internal Error: Insane row size!" << std::endl
;
139 ssize_t
align_position(size_t space
, size_t size
, int32_t pos
)
141 ssize_t freespace
= space
- size
;
142 return ((pos
+ 1000) * freespace
) / 2000;
145 void ft_render_text_line(FT_Face face
, const std::string
& text
, std::vector
<uint8_t>& data
, size_t width
,
146 size_t height
, ssize_t alignment
, size_t y
, struct render_line_info l
)
148 ssize_t p
= align_position(width
, l
.width
, alignment
);
149 if(y
>= height
|| p
>= static_cast<ssize_t
>(width
))
150 return; //Nothing fits.
154 uint16_t state
= utf8_initial_state
;
155 size_t len
= text
.length();
156 //We need to compute the window offset.
157 offset_y
= l
.baseline
+ y
;
158 offset_x
= l
.offset
+ p
;
159 for(size_t i
= 0; i
<= len
; i
++) {
160 int32_t sym
= utf8_parse_byte((i
< len
) ? static_cast<uint8_t>(text
[i
]) : -1, state
);
163 //Got unicode symbol.
164 unsigned gslot
= FT_Get_Char_Index(face
, sym
);
167 std::cerr
<< "Warning: Symbol " << sym
<< " not present in font!" << std::endl
;
171 throw_ft_error(FT_Load_Glyph(face
, gslot
, FT_LOAD_DEFAULT
));
172 throw_ft_error(FT_Render_Glyph(face
->glyph
, FT_RENDER_MODE_MONO
));
173 } catch(std::exception
& e
) {
174 std::cerr
<< "Warning: Symbol " << sym
<< " can't be rendered: " << e
.what()
178 ssize_t render_x
= basepos
+ face
->glyph
->bitmap_left
+ offset_x
;
179 ssize_t render_y
= -face
->glyph
->bitmap_top
+ offset_y
;
180 for(int j
= 0; (unsigned)j
< face
->glyph
->bitmap
.rows
&& render_y
+ j
<
181 static_cast<ssize_t
>(height
); j
++) {
184 const uint8_t* ldata
= face
->glyph
->bitmap
.buffer
+ j
* face
->glyph
->bitmap
.pitch
;
185 uint8_t* tmpr
= &data
[(render_y
+ j
) * width
];
186 for(int k
= 0; (unsigned)k
< face
->glyph
->bitmap
.width
&& render_x
+ k
<
187 static_cast<ssize_t
>(width
); k
++) {
190 tmpr
[render_x
+ k
] = ((ldata
[k
>> 3] >> (7 - (k
& 7))) & 1) ? 1 : 0;
193 basepos
= basepos
+ (face
->glyph
->advance
.x
>> 6);
197 void ft_render_text(FT_Face face
, const std::string
& text
, std::vector
<uint8_t>& data
, size_t width
,
198 size_t height
, ssize_t alignment
, size_t spacing
, size_t y
= 0)
200 std::string _text
= text
;
201 //If the string contains linefeeds, break it up.
202 size_t p
= _text
.find("\\n");
203 if(p
< _text
.length()) {
204 std::string t1
= _text
.substr(0, p
);
205 std::string t2
= _text
.substr(p
+ 2);
206 auto g1
= ft_rendered_size_line(face
, t1
);
207 ft_render_text_line(face
, t1
, data
, width
, height
, alignment
, y
, g1
);
208 ft_render_text(face
, t2
, data
, width
, height
, alignment
, spacing
, y
+ g1
.height
+ spacing
);
211 //The text is in one piece if this place is reached.
212 auto g1
= ft_rendered_size_line(face
, text
);
213 ft_render_text_line(face
, text
, data
, width
, height
, alignment
, y
, g1
);
216 std::pair
<size_t, size_t> ft_rendered_size(FT_Face face
, const std::string
& text
, size_t spacing
)
218 std::string _text
= text
;
219 //If the string contains linefeeds, break it up.
220 size_t p
= _text
.find("\\n");
221 if(p
< _text
.length()) {
222 std::string t1
= _text
.substr(0, p
);
223 std::string t2
= _text
.substr(p
+ 2);
224 auto g1
= ft_rendered_size_line(face
, t1
);
225 auto g2
= ft_rendered_size(face
, t2
, spacing
);
227 w
= (g1
.width
> g2
.first
) ? g1
.width
: g2
.first
;
228 h
= g1
.height
+ spacing
+ g2
.second
;
229 return std::make_pair(w
, h
);
231 //The text is in one piece if this place is reached.
232 auto g
= ft_rendered_size_line(face
, text
);
233 return std::make_pair(g
.width
, g
.height
);
236 std::string
read_subtitle_from_file(std::string filename
)
240 std::ifstream
x(filename
);
242 throw std::runtime_error("Can't open '" + filename
+ "'");
243 while(std::getline(x
, tmp
)) {
245 content
= content
+ "\n" + tmp
;
249 std::ostringstream content2
;
250 for(size_t i
= 0; i
< content
.length(); i
++)
251 if(content
[i
] == '\n')
253 else if(content
[i
] == '\\')
256 content2
<< content
[i
];
257 return content2
.str();
260 ssize_t
position_subtitle(size_t space
, size_t size
, int32_t pos
, bool absolute
)
265 ssize_t freespace
= space
- size
;
266 return ((pos
+ 1000) * freespace
) / 2000;
270 yuvc_converter
* get_converter(const regex_results
& r
)
272 std::string csp
= "rec601";
275 yuvc_converter
* c
= yuvc_get_converter(RGB_RGB
, csp
);
279 std::cerr
<< "Warning: Stream YUV variant unspecified, assuming rec601." << std::endl
;
281 std::cerr
<< "Warning: Stream YUV variant unsupported, falling back to rec601." << std::endl
;
282 return yuvc_get_converter(RGB_RGB
, "rec601");
285 void stamp_pos(size_t space
, size_t dimension
, ssize_t pos
, size_t& fpos
, size_t& spos
, size_t& sdim
)
304 else if(fpos
+ sdim
> space
)
309 std::pair
<size_t, size_t> text_render_parameters::render_size(const std::string
& text
)
313 //Init freetype2 if needed.
315 throw_ft_error(FT_Init_FreeType(&ft_handle
));
318 //Load the font face.
320 throw std::runtime_error("Font file needed for text rendering");
321 throw_ft_error(FT_New_Face(ft_handle
, fontfile
.c_str(), fontface
, &face
));
322 throw_ft_error(FT_Set_Pixel_Sizes(face
, 0, fontsize
));
324 auto g
= ft_rendered_size(face
, text
, text_spacing
);
325 return std::make_pair(g
.first
+ 2 * halo_thickness
, g
.second
+ 2 * halo_thickness
);
328 parsed_png
& text_render_parameters::render(const std::string
& text
)
331 std::vector
<uint8_t> tmp
;
332 size_t width
, height
;
333 size_t width2
, height2
;
335 //Init freetype2 if needed.
337 throw_ft_error(FT_Init_FreeType(&ft_handle
));
340 //Load the font face.
342 throw std::runtime_error("Font file needed for text rendering");
343 throw_ft_error(FT_New_Face(ft_handle
, fontfile
.c_str(), fontface
, &face
));
344 throw_ft_error(FT_Set_Pixel_Sizes(face
, 0, fontsize
));
346 //First we need to figure out the size of the rendered bitmap. Then render the actual bitmap.
347 auto g
= ft_rendered_size(face
, text
, text_spacing
);
350 width2
= width
+ 2 * halo_thickness
;
351 height2
= height
+ 2 * halo_thickness
;
352 tmp
.resize(width2
* height2
);
353 memset(&tmp
[0], 0, width2
* height2
);
354 ft_render_text(face
, text
, tmp
, width
, height
, text_alignment
, text_spacing
);
356 //Do in-place padding by halo_thickness on all sides.
357 if(halo_thickness
> 0) {
358 for(size_t i
= height
- 1; i
< height
; i
--)
359 memmove(&tmp
[width2
* (i
+ halo_thickness
) + halo_thickness
], &tmp
[width
* i
], width
);
360 for(size_t i
= 0; i
< height
; i
++) {
361 memset(&tmp
[width2
* (i
+ halo_thickness
)], 0, halo_thickness
);
362 memset(&tmp
[width2
* (i
+ halo_thickness
+ 1) - halo_thickness
], 0, halo_thickness
);
364 memset(&tmp
[0], 0, width2
* halo_thickness
);
365 render_halo(&tmp
[0], &tmp
[0], width2
, height2
, halo_thickness
);
368 parsed_png
& png
= *new parsed_png(width2
, height2
);
369 for(size_t i
= 0; i
< width2
* height2
; i
++) {
370 if(tmp
[i
] == 0) png
.data
[i
] = bg_color
;
371 if(tmp
[i
] == 1) png
.data
[i
] = fg_color
;
372 if(tmp
[i
] == 2) png
.data
[i
] = halo_color
;
377 text_render_parameters::text_render_parameters()
383 fg_color
= 0xFFFFFFFFU
;
384 halo_color
= 0xFF000000U
;
389 bool text_render_parameters::argument(const std::string
& arg
)
392 if(r
= regex("--font-file=(.*)", arg
)) {
395 } else if(r
= regex("--font-size=(.*)", arg
)) {
396 fontsize
= parse_value
<unsigned>(r
[1]);
398 } else if(r
= regex("--text-alignment=left", arg
)) {
399 text_alignment
= -1000;
401 } else if(r
= regex("--text-alignment=center", arg
)) {
404 } else if(r
= regex("--text-alignment=right", arg
)) {
405 text_alignment
= 1000;
407 } else if(r
= regex("--text-alignment=(.*)", arg
)) {
408 text_alignment
= parse_value
<signed>(r
[1]);
410 } else if(r
= regex("--text-spacing=(.*)", arg
)) {
411 text_spacing
= parse_value
<signed>(r
[1]);
413 } else if(r
= regex("--font-face=(.*)", arg
)) {
414 fontface
= parse_value
<long>(r
[1]);
416 } else if(r
= regex("--fg-color=(.*),(.*),(.*),(.*)", arg
)) {
417 unsigned R
= parse_value
<unsigned>(r
[1]);
418 unsigned g
= parse_value
<unsigned>(r
[2]);
419 unsigned b
= parse_value
<unsigned>(r
[3]);
420 unsigned a
= parse_value
<unsigned>(r
[4]);
421 fg_color
= (a
<< 24) | (b
<< 16) | (g
<< 8) | R
;
423 } else if(r
= regex("--fg-color=(.*),(.*),(.*)", arg
)) {
424 unsigned R
= parse_value
<unsigned>(r
[1]);
425 unsigned g
= parse_value
<unsigned>(r
[2]);
426 unsigned b
= parse_value
<unsigned>(r
[3]);
427 fg_color
= (fg_color
& 0xFF000000U
) | (b
<< 16) | (g
<< 8) | R
;
429 } else if(r
= regex("--fg-alpha=(.*)", arg
)) {
430 unsigned a
= parse_value
<unsigned>(r
[1]);
431 fg_color
= (fg_color
& 0xFFFFFFU
) | (a
<< 24);
433 } else if(r
= regex("--bg-color=(.*),(.*),(.*),(.*)", arg
)) {
434 unsigned R
= parse_value
<unsigned>(r
[1]);
435 unsigned g
= parse_value
<unsigned>(r
[2]);
436 unsigned b
= parse_value
<unsigned>(r
[3]);
437 unsigned a
= parse_value
<unsigned>(r
[4]);
438 bg_color
= (a
<< 24) | (b
<< 16) | (g
<< 8) | R
;
440 } else if(r
= regex("--bg-color=(.*),(.*),(.*)", arg
)) {
441 unsigned R
= parse_value
<unsigned>(r
[1]);
442 unsigned g
= parse_value
<unsigned>(r
[2]);
443 unsigned b
= parse_value
<unsigned>(r
[3]);
444 bg_color
= (bg_color
& 0xFF000000U
) | (b
<< 16) | (g
<< 8) | R
;
446 } else if(r
= regex("--bg-alpha=(.*)", arg
)) {
447 unsigned a
= parse_value
<unsigned>(r
[1]);
448 bg_color
= (bg_color
& 0xFFFFFFU
) | (a
<< 24);
450 } else if(r
= regex("--halo-color=(.*),(.*),(.*),(.*)", arg
)) {
451 unsigned R
= parse_value
<unsigned>(r
[1]);
452 unsigned g
= parse_value
<unsigned>(r
[2]);
453 unsigned b
= parse_value
<unsigned>(r
[3]);
454 unsigned a
= parse_value
<unsigned>(r
[4]);
455 halo_color
= (a
<< 24) | (b
<< 16) | (g
<< 8) | R
;
457 } else if(r
= regex("--halo-color=(.*),(.*),(.*)", arg
)) {
458 unsigned R
= parse_value
<unsigned>(r
[1]);
459 unsigned g
= parse_value
<unsigned>(r
[2]);
460 unsigned b
= parse_value
<unsigned>(r
[3]);
461 halo_color
= (halo_color
& 0xFF000000U
) | (b
<< 16) | (g
<< 8) | R
;
463 } else if(r
= regex("--halo-alpha=(.*)", arg
)) {
464 unsigned a
= parse_value
<unsigned>(r
[1]);
465 halo_color
= (halo_color
& 0xFFFFFFU
) | (a
<< 24);
467 } else if(r
= regex("--halo-thickness=(.*)", arg
)) {
468 halo_thickness
= parse_value
<unsigned>(r
[1]);
469 if(halo_thickness
< 0)
476 pre_subtitle::pre_subtitle()
481 pre_subtitle::~pre_subtitle()
486 subtitle::subtitle(const pre_subtitle
& presub
, const yuv4mpeg_stream_header
& strmh
)
489 if(strmh
.fps_n
&& strmh
.fps_d
)
490 fps
= 1.0 * strmh
.fps_n
/ strmh
.fps_d
;
491 frame
= presub
.start
.get_frame(fps
, 0);
492 duration
= presub
.duration
.get_frame(fps
, 0);
493 width
= presub
.image
->width
;
494 height
= presub
.image
->height
;
495 x
= position_subtitle(strmh
.width
, width
, presub
.xpos
, presub
.xabsolute
);
496 y
= position_subtitle(strmh
.height
, height
, presub
.ypos
, presub
.yabsolute
);
497 if(x
< 0 || y
< 0 || x
+ width
> strmh
.width
|| y
+ height
> strmh
.height
)
498 std::cerr
<< "Warning: Subtitle is partially offscreen" << std::endl
;
500 if(strmh
.chroma
== "rgb") {
502 data
= new uint8_t[width
* height
* 3];
504 planesep
= width
* height
* 3;
505 for(size_t i
= 0; i
< width
* height
; i
++) {
506 data
[3 * i
+ 0] = presub
.image
->data
[i
];
507 data
[3 * i
+ 1] = presub
.image
->data
[i
] >> 8;
508 data
[3 * i
+ 2] = presub
.image
->data
[i
] >> 16;
510 } else if(strmh
.chroma
== "444" || strmh
.chroma
== "444p16") {
511 std::vector
<uint8_t> tmp
;
512 bits16
= (strmh
.chroma
== "444p16");
513 tmp
.resize(width
* height
* 3);
514 data
= new uint8_t[width
* height
* (bits16
? 6 : 3)];
515 uint16_t* data16
= reinterpret_cast<uint16_t*>(data
);
517 planesep
= width
* height
;
518 yuvc_converter
* conv
= get_converter(strmh
.find_extension("yuvmatrix=(.*)"));
519 for(size_t i
= 0; i
< width
* height
; i
++) {
520 tmp
[3 * i
+ 0] = presub
.image
->data
[i
];
521 tmp
[3 * i
+ 1] = presub
.image
->data
[i
] >> 8;
522 tmp
[3 * i
+ 2] = presub
.image
->data
[i
] >> 16;
525 conv
->transform(data16
, &tmp
[0], width
* height
);
527 conv
->transform(data
, &tmp
[0], width
* height
);
529 (stringfmt() << "Chroma type " << strmh
.chroma
<< " not supported for subtitling").throwex();
530 alpha
= new uint32_t[width
* height
];
532 for(size_t i
= 0; i
< width
* height
; i
++)
533 alpha
[i
] = ((presub
.image
->data
[i
] >> 24) + (presub
.image
->data
[i
] >> 31)) << 8;
535 for(size_t i
= 0; i
< width
* height
; i
++)
536 alpha
[i
] = (presub
.image
->data
[i
] >> 24) + (presub
.image
->data
[i
] >> 31);
539 subtitle::~subtitle()
546 subtitle_render_context::subtitle_render_context()
552 duration
= timemarker("5.0");
555 bool subtitle_render_context::argument(const std::string
& arg
, pre_subtitle
*& presub
)
558 if(tparams
.argument(arg
))
560 if(r
= regex("--(text|file|png)=([^,]*),(.*)", arg
)) {
561 std::string text
= r
[3];
562 timemarker
start(r
[2]);
565 text
= read_subtitle_from_file(text
);
567 png
= new parsed_png(text
);
569 png
= &tparams
.render(text
);
570 presub
= new pre_subtitle
;
574 presub
->xabsolute
= xabsolute
;
575 presub
->yabsolute
= yabsolute
;
576 presub
->start
= start
;
577 presub
->duration
= duration
;
579 } else if(r
= regex("--duration=(.*)", arg
)) {
580 duration
= timemarker(r
[1]);
582 } else if(r
= regex("--xpos=left", arg
)) {
586 } else if(r
= regex("--xpos=center", arg
)) {
590 } else if(r
= regex("--xpos=right", arg
)) {
594 } else if(r
= regex("--xpos=abs:([+-]?[0-9]+)", arg
)) {
596 xpos
= parse_value
<ssize_t
>(r
[1]);
598 } else if(r
= regex("--xpos=([+-]?[0-9]+)", arg
)) {
600 xpos
= parse_value
<ssize_t
>(r
[1]);
602 } else if(r
= regex("--ypos=top", arg
)) {
606 } else if(r
= regex("--ypos=center", arg
)) {
610 } else if(r
= regex("--ypos=bottom", arg
)) {
614 } else if(r
= regex("--ypos=abs:([+-]?[0-9]+)", arg
)) {
616 ypos
= parse_value
<ssize_t
>(r
[1]);
618 } else if(r
= regex("--ypos=([+-]?[0-9]+)", arg
)) {
620 ypos
= parse_value
<ssize_t
>(r
[1]);
627 std::vector
<subtitle
*> subtitle::from_presub(const std::vector
<pre_subtitle
*>& presubs
,
628 const yuv4mpeg_stream_header
& strmh
)
630 std::vector
<subtitle
*> ret
;
631 for(auto i
: presubs
) {
633 ret
.push_back(new subtitle(*i
, strmh
));
642 void subtitle::stamp(uint8_t* idata
, size_t iwidth
, size_t iheight
, uint64_t frameno
) const
644 if(frameno
< frame
|| frameno
> frame
+ duration
)
646 size_t fpos_x
, spos_x
, swidth
;
647 size_t fpos_y
, spos_y
, sheight
;
648 stamp_pos(iwidth
, width
, x
, fpos_x
, spos_x
, swidth
);
649 stamp_pos(iheight
, height
, y
, fpos_y
, spos_y
, sheight
);
650 if(!swidth
|| !sheight
)
652 if(planes
== 3 && bits16
) {
653 size_t iplanesep
= iwidth
* iheight
;
654 uint16_t* idata2
= reinterpret_cast<uint16_t*>(idata
);
655 const uint16_t* data2
= reinterpret_cast<const uint16_t*>(data
);
656 for(size_t i
= 0; i
< sheight
; i
++)
657 for(size_t j
= 0; j
< swidth
; j
++) {
658 size_t ioffset
= (fpos_y
+ i
) * iwidth
+ (fpos_x
+ j
);
659 size_t offset
= (spos_y
+ i
) * width
+ (spos_x
+ j
);
660 mix(idata2
[ioffset
], data2
[offset
], alpha
[offset
]);
661 mix(idata2
[ioffset
+ iplanesep
], data2
[offset
+ planesep
], alpha
[offset
]);
662 mix(idata2
[ioffset
+ iplanesep
], data2
[offset
+ planesep
], alpha
[offset
]);
664 } else if(planes
== 3 && !bits16
) {
665 size_t iplanesep
= iwidth
* iheight
;
666 for(size_t i
= 0; i
< sheight
; i
++)
667 for(size_t j
= 0; j
< swidth
; j
++) {
668 size_t ioffset
= (fpos_y
+ i
) * iwidth
+ (fpos_x
+ j
);
669 size_t offset
= (spos_y
+ i
) * width
+ (spos_x
+ j
);
670 mix(idata
[ioffset
], data
[offset
], alpha
[offset
]);
671 mix(idata
[ioffset
+ iplanesep
], data
[offset
+ planesep
], alpha
[offset
]);
672 mix(idata
[ioffset
+ 2 * iplanesep
], data
[offset
+ 2 * planesep
], alpha
[offset
]);
674 } else if(planes
== 1) {
675 for(size_t i
= 0; i
< sheight
; i
++)
676 for(size_t j
= 0; j
< swidth
; j
++) {
677 size_t ioffset
= (fpos_y
+ i
) * 3 * iwidth
+ 3 * (fpos_x
+ j
);
678 size_t offset2
= (spos_y
+ i
) * width
+ (spos_x
+ j
);
679 size_t offset
= offset2
* 3;
680 mix(idata
[ioffset
], data
[offset
], alpha
[offset2
]);
681 mix(idata
[ioffset
+ 1], data
[offset
+ 1], alpha
[offset2
]);
682 mix(idata
[ioffset
+ 2], data
[offset
+ 2], alpha
[offset2
]);