/src/vvenc/source/Lib/apputils/LogoRenderer.h
Line | Count | Source |
1 | | /* ----------------------------------------------------------------------------- |
2 | | The copyright in this software is being made available under the Clear BSD |
3 | | License, included below. No patent rights, trademark rights and/or |
4 | | other Intellectual Property Rights other than the copyrights concerning |
5 | | the Software are granted under this license. |
6 | | |
7 | | The Clear BSD License |
8 | | |
9 | | Copyright (c) 2019-2026, Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. & The VVenC Authors. |
10 | | All rights reserved. |
11 | | |
12 | | Redistribution and use in source and binary forms, with or without modification, |
13 | | are permitted (subject to the limitations in the disclaimer below) provided that |
14 | | the following conditions are met: |
15 | | |
16 | | * Redistributions of source code must retain the above copyright notice, |
17 | | this list of conditions and the following disclaimer. |
18 | | |
19 | | * Redistributions in binary form must reproduce the above copyright |
20 | | notice, this list of conditions and the following disclaimer in the |
21 | | documentation and/or other materials provided with the distribution. |
22 | | |
23 | | * Neither the name of the copyright holder nor the names of its |
24 | | contributors may be used to endorse or promote products derived from this |
25 | | software without specific prior written permission. |
26 | | |
27 | | NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY |
28 | | THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND |
29 | | CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
30 | | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A |
31 | | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR |
32 | | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
33 | | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
34 | | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR |
35 | | BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER |
36 | | IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
37 | | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
38 | | POSSIBILITY OF SUCH DAMAGE. |
39 | | |
40 | | |
41 | | ------------------------------------------------------------------------------------------- */ |
42 | | /** \file LogoRenderer.h |
43 | | \brief yuv logo renderer class (header) |
44 | | */ |
45 | | |
46 | | #pragma once |
47 | | |
48 | | #include <iostream> |
49 | | #include <fstream> |
50 | | #include <sstream> |
51 | | #include <iomanip> |
52 | | #include <string> |
53 | | #include <algorithm> |
54 | | |
55 | | #include "vvenc/version.h" |
56 | | #include "vvenc/vvenc.h" |
57 | | |
58 | | #include "FileIOHelper.h" |
59 | | |
60 | | #ifdef VVENC_ENABLE_THIRDPARTY_JSON |
61 | | #include <nlohmann/json.hpp> |
62 | | using nlohmann::json; |
63 | | #endif |
64 | | |
65 | | |
66 | | //! \ingroup Interface |
67 | | //! \{ |
68 | | |
69 | | namespace apputils { |
70 | | |
71 | | // ==================================================================================================================== |
72 | | |
73 | | struct LogoInputOptions |
74 | | { |
75 | | std::string logoFilename; |
76 | | int sourceWidth = 0; |
77 | | int sourceHeight = 0; |
78 | | int bitdepth = 10; |
79 | | int bgColorMin = -1; |
80 | | int bgColorMax = -1; |
81 | | }; |
82 | | |
83 | | struct LogoRenderOptions |
84 | | { |
85 | | int offsetHor = 0; // horizontal offset ( >= 0 offset from left, < 0 offset from right) |
86 | | int offsetVer = 0; // vertical offset ( >= 0 offset from top, < 0 offset from bottom) |
87 | | int opacity = 0; |
88 | | }; |
89 | | |
90 | | struct LogoOverlay |
91 | | { |
92 | | std::string version = VVENC_VERSION; |
93 | | LogoInputOptions inputOpts; |
94 | | LogoRenderOptions renderOpts; |
95 | | }; |
96 | | |
97 | | #ifdef VVENC_ENABLE_THIRDPARTY_JSON |
98 | | inline void to_json( json& j, const LogoInputOptions& l) |
99 | 0 | { |
100 | 0 | j = json{ |
101 | 0 | { "//LogoFilename", "path to yuv/y4m logo file can be defined as absolute path or relative path from json file" }, |
102 | 0 | { "LogoFilename", l.logoFilename }, |
103 | 0 | { "SourceWidth", l.sourceWidth }, |
104 | 0 | { "SourceHeight", l.sourceHeight }, |
105 | 0 | { "InputBitDepth", l.bitdepth }, |
106 | 0 | { "//BgColorMinMax", "defines background color (min-max range inclusive). color range is removed when >= 0" }, |
107 | 0 | { "BgColorMin", l.bgColorMin }, |
108 | 0 | { "BgColorMax", l.bgColorMax } |
109 | 0 | }; |
110 | 0 | } |
111 | | |
112 | | inline void to_json( json& j, const LogoRenderOptions& r) |
113 | 0 | { |
114 | 0 | j = json{ |
115 | 0 | { "//OffsetHor", "defines logo horizontal offset(x) in px. (clipped to pic borders), if >= 0 offset from left, < 0 offset from right+1" }, |
116 | 0 | { "//OffsetVer", "defines logo vertical offset(y) in px. (clipped to pic borders), if >= 0 offset from top, < 0 offset from bottom+1" }, |
117 | 0 | { "OffsetHor", r.offsetHor }, |
118 | 0 | { "OffsetVer", r.offsetVer }, |
119 | 0 | { "//Opacity", "defines opacity level in range 0-100% (0: opaque - 100: transparent)" }, |
120 | 0 | { "Opacity", r.opacity } |
121 | 0 | }; |
122 | 0 | } |
123 | | |
124 | | inline void to_json( json& j, const LogoOverlay& l) |
125 | 0 | { |
126 | 0 | j = json{ |
127 | 0 | { "version", l.version }, |
128 | 0 | { "input_opts", l.inputOpts }, |
129 | 0 | { "render_opts", l.renderOpts }, |
130 | 0 | }; |
131 | 0 | } |
132 | | |
133 | | inline void from_json(const json& j, LogoInputOptions& l) |
134 | 0 | { |
135 | 0 | j.at("LogoFilename").get_to(l.logoFilename); |
136 | 0 | j.at("SourceWidth").get_to(l.sourceWidth); |
137 | 0 | j.at("SourceHeight").get_to(l.sourceHeight); |
138 | 0 | j.at("InputBitDepth").get_to(l.bitdepth); |
139 | 0 | j.at("BgColorMin").get_to(l.bgColorMin); |
140 | 0 | j.at("BgColorMax").get_to(l.bgColorMax); |
141 | 0 | } |
142 | | |
143 | | inline void from_json(const json& j, LogoRenderOptions& l ) |
144 | 0 | { |
145 | 0 | j.at("OffsetHor").get_to(l.offsetHor); |
146 | 0 | j.at("OffsetVer").get_to(l.offsetVer); |
147 | 0 | j.at("Opacity").get_to(l.opacity); |
148 | 0 | } |
149 | | |
150 | | inline void from_json(const json& j, LogoOverlay& l ) |
151 | 0 | { |
152 | 0 | j.at("version").get_to(l.version); |
153 | 0 | j.at("input_opts").get_to(l.inputOpts); |
154 | 0 | j.at("render_opts").get_to(l.renderOpts); |
155 | 0 | } |
156 | | #endif |
157 | | |
158 | | class LogoRenderer |
159 | | { |
160 | | public: |
161 | | LogoRenderer() |
162 | 0 | { |
163 | 0 | } |
164 | | |
165 | | ~LogoRenderer() |
166 | 0 | { |
167 | 0 | if( m_bInitialized ){ uninit(); } |
168 | 0 | } |
169 | | |
170 | | int init( const std::string &fileName, vvencChromaFormat chromaFormat, std::ostream& rcOstr ) |
171 | 0 | { |
172 | 0 | if( m_bInitialized ) |
173 | 0 | { |
174 | 0 | rcOstr << "error: logo overlay already initialized." << std::endl; |
175 | 0 | return -1; |
176 | 0 | } |
177 | 0 | #ifndef VVENC_ENABLE_THIRDPARTY_JSON |
178 | 0 | rcOstr << "error: logo overlay is not supported. please compile with json enabled" << std::endl; |
179 | 0 | return -1; |
180 | 0 | #endif |
181 | 0 | |
182 | 0 | m_chromaFormat = chromaFormat; |
183 | 0 | if( readLogoFile( fileName, rcOstr ) ) |
184 | 0 | { |
185 | 0 | rcOstr << "error reading json logo file with logo description:" << std::endl; |
186 | 0 | dumpOutput( rcOstr ); |
187 | 0 | return -1; |
188 | 0 | } |
189 | 0 | |
190 | 0 | bool isY4mInput = false; |
191 | 0 | if( FileIOHelper::isY4mInputFilename( m_cLogo.inputOpts.logoFilename ) ) |
192 | 0 | { |
193 | 0 | vvenc_config c; |
194 | 0 | if ( 0 > FileIOHelper::parseY4mHeader( m_cLogo.inputOpts.logoFilename, c, m_chromaFormat ) ) |
195 | 0 | { |
196 | 0 | rcOstr << "cannot parse y4m information in file " << m_cLogo.inputOpts.logoFilename << std::endl; |
197 | 0 | return -1; |
198 | 0 | } |
199 | 0 | m_cLogo.inputOpts.sourceWidth = c.m_SourceWidth; |
200 | 0 | m_cLogo.inputOpts.sourceHeight = c.m_SourceHeight; |
201 | 0 | m_cLogo.inputOpts.bitdepth = c.m_inputBitDepth[0]; |
202 | 0 | isY4mInput = true; |
203 | 0 | } |
204 | 0 |
|
205 | 0 | std::string cErr; |
206 | 0 | if( !FileIOHelper::checkInputFile( m_cLogo.inputOpts.logoFilename, cErr ) ) |
207 | 0 | { |
208 | 0 | rcOstr << "Logo input file error: " << cErr << std::endl; |
209 | 0 | return -1; |
210 | 0 | } |
211 | 0 | |
212 | 0 | if( m_cLogo.inputOpts.sourceWidth <= 0 || m_cLogo.inputOpts.sourceHeight <= 0 ) |
213 | 0 | { |
214 | 0 | rcOstr << "Logo input file error: invalid size " << m_cLogo.inputOpts.sourceWidth << "x" << m_cLogo.inputOpts.sourceHeight << std::endl; |
215 | 0 | return -1; |
216 | 0 | } |
217 | 0 | |
218 | 0 | if( m_cLogo.inputOpts.bitdepth == 8 ) // input must be 10bit -> transpose min/max to 10bit |
219 | 0 | { |
220 | 0 | m_cLogo.inputOpts.bgColorMin = m_cLogo.inputOpts.bgColorMin << 2; |
221 | 0 | m_cLogo.inputOpts.bgColorMax = m_cLogo.inputOpts.bgColorMax << 2; |
222 | 0 | } |
223 | 0 | |
224 | 0 | vvenc_YUVBuffer_default( &m_cYuvBufLogo ); |
225 | 0 | vvenc_YUVBuffer_alloc_buffer( &m_cYuvBufLogo, chromaFormat, m_cLogo.inputOpts.sourceWidth, m_cLogo.inputOpts.sourceHeight ); |
226 | 0 | |
227 | 0 | if( m_cLogo.renderOpts.opacity >= 100 ) |
228 | 0 | { |
229 | 0 | m_bBypass = true; |
230 | 0 | } |
231 | 0 | else |
232 | 0 | { |
233 | 0 | m_bBypass = false; |
234 | 0 | if( (m_cLogo.inputOpts.bgColorMin >= 0 && m_cLogo.inputOpts.bgColorMax >= 0 ) || |
235 | 0 | m_cLogo.renderOpts.opacity > 0 ) |
236 | 0 | { |
237 | 0 | m_bAlphaNeeded = true; |
238 | 0 | vvenc_YUVBuffer_default( &m_cYuvBufAlpha ); |
239 | 0 | vvenc_YUVBuffer_alloc_buffer( &m_cYuvBufAlpha, VVENC_CHROMA_400, m_cLogo.inputOpts.sourceWidth, m_cLogo.inputOpts.sourceHeight ); |
240 | 0 | } |
241 | 0 | } |
242 | 0 |
|
243 | 0 | std::fstream cLogoHandle; |
244 | 0 | cLogoHandle.open( m_cLogo.inputOpts.logoFilename.c_str(), std::ios::binary | std::ios::in ); |
245 | 0 | if( cLogoHandle.fail() ) |
246 | 0 | { |
247 | 0 | rcOstr << "Failed to open logo overlay file: " << m_cLogo.inputOpts.logoFilename << std::endl; |
248 | 0 | return -1; |
249 | 0 | } |
250 | 0 | |
251 | 0 | if ( isY4mInput ) |
252 | 0 | { |
253 | 0 | std::string headerline; |
254 | 0 | getline(cLogoHandle, headerline); // jump over y4m header |
255 | 0 | |
256 | 0 | std::string y4mPrefix; |
257 | 0 | getline(cLogoHandle, y4mPrefix); /* assume basic FRAME\n headers */ |
258 | 0 | if( y4mPrefix != "FRAME") |
259 | 0 | { |
260 | 0 | rcOstr << "Source image does not contain valid y4m header (FRAME) - end of stream" << std::endl; |
261 | 0 | return -1; |
262 | 0 | } |
263 | 0 | } |
264 | 0 |
|
265 | 0 | // read the logo int yuvBuffer |
266 | 0 | bool is16bit = m_cLogo.inputOpts.bitdepth > 8 ? true : false; |
267 | 0 | int bitdepthShift = 10 - m_cLogo.inputOpts.bitdepth; |
268 | 0 | const LPel maxVal = ( 1 << 10 ) - 1; |
269 | 0 | |
270 | 0 | for( int comp = 0; comp < 3; comp++ ) |
271 | 0 | { |
272 | 0 | vvencYUVPlane yuvPlane = m_cYuvBufLogo.planes[ comp ]; |
273 | 0 | if ( ! FileIOHelper::readYuvPlane( cLogoHandle, yuvPlane, is16bit, m_cLogo.inputOpts.bitdepth, false, comp, m_chromaFormat, m_chromaFormat ) ) |
274 | 0 | { |
275 | 0 | rcOstr << "Failed to read plane from logo overlay file: " << m_cLogo.inputOpts.logoFilename << std::endl; |
276 | 0 | return -1; |
277 | 0 | } |
278 | 0 | |
279 | 0 | if ( m_chromaFormat == VVENC_CHROMA_400 && comp) |
280 | 0 | continue; |
281 | 0 | |
282 | 0 | if ( ! FileIOHelper::verifyYuvPlane( yuvPlane, m_cLogo.inputOpts.bitdepth ) ) |
283 | 0 | { |
284 | 0 | rcOstr << "Logo image contains values outside the specified bit range!" << std::endl; |
285 | 0 | return -1; |
286 | 0 | } |
287 | 0 | |
288 | 0 | FileIOHelper::scaleYuvPlane( yuvPlane, yuvPlane, bitdepthShift, 0, maxVal ); |
289 | 0 | } |
290 | 0 |
|
291 | 0 |
|
292 | 0 | if( !m_bBypass && m_bAlphaNeeded ) |
293 | 0 | { |
294 | 0 | initAlphaMask(); |
295 | 0 | } |
296 | 0 |
|
297 | 0 | m_bInitialized = true; |
298 | 0 | |
299 | 0 | return 0; |
300 | 0 | } |
301 | | |
302 | | int uninit() |
303 | 0 | { |
304 | 0 | if( !m_bInitialized ) |
305 | 0 | { |
306 | 0 | return -1; |
307 | 0 | } |
308 | 0 | |
309 | 0 | vvenc_YUVBuffer_free_buffer( &m_cYuvBufLogo ); |
310 | 0 | if ( m_bAlphaNeeded ) |
311 | 0 | { |
312 | 0 | vvenc_YUVBuffer_free_buffer( &m_cYuvBufAlpha ); |
313 | 0 | } |
314 | 0 | |
315 | 0 | m_bInitialized = false; |
316 | 0 | return 0; |
317 | 0 | } |
318 | | |
319 | 0 | bool isInitialized() const { return m_bInitialized; } |
320 | | |
321 | 0 | LogoInputOptions getLogoInputOptions() { return m_cLogo.inputOpts; } |
322 | 0 | vvencYUVBuffer* getLogoYuvBuffer() { return &m_cYuvBufLogo; } |
323 | | |
324 | | void dumpOutput( std::ostream& rcOstr ) |
325 | 0 | { |
326 | 0 | #ifdef VVENC_ENABLE_THIRDPARTY_JSON |
327 | 0 | const json j { m_cLogo }; |
328 | 0 | rcOstr << j.dump(2) << std::endl; |
329 | 0 | #else |
330 | 0 | rcOstr << std::endl; |
331 | 0 | #endif |
332 | 0 | } |
333 | | |
334 | | int writeLogoFile( std::string fileName, std::ostream& rcOstr ) |
335 | 0 | { |
336 | 0 | #ifdef VVENC_ENABLE_THIRDPARTY_JSON |
337 | 0 | std::fstream logoFHandle; |
338 | 0 | logoFHandle.open( fileName, std::ios::out ); |
339 | 0 | if ( logoFHandle.fail() ) |
340 | 0 | { |
341 | 0 | rcOstr << "error: cannot open logo overlay file '" << fileName << "'." << std::endl; |
342 | 0 | return -1; |
343 | 0 | } |
344 | 0 | |
345 | 0 | const json j { m_cLogo }; |
346 | 0 | logoFHandle << std::setw(4) << j << std::endl; |
347 | 0 | |
348 | 0 | if( logoFHandle.is_open() ) |
349 | 0 | logoFHandle.close(); |
350 | 0 | return 0; |
351 | 0 | #else |
352 | 0 | rcOstr << "error: cannot write logo overlay file '" << fileName << "'. json not enabled" << std::endl; |
353 | 0 | return -1; |
354 | 0 | #endif |
355 | 0 | } |
356 | | |
357 | | int readLogoFile( std::string fileName, std::ostream& rcOstr ) |
358 | 0 | { |
359 | 0 | #ifdef VVENC_ENABLE_THIRDPARTY_JSON |
360 | 0 | std::fstream logoFHandle; |
361 | 0 | logoFHandle.open( fileName, std::ios::in ); |
362 | 0 | if ( logoFHandle.fail() ) |
363 | 0 | { |
364 | 0 | rcOstr << "error: cannot open logo overlay file '" << fileName << "'." << std::endl; |
365 | 0 | return -1; |
366 | 0 | } |
367 | 0 | |
368 | 0 | try |
369 | 0 | { |
370 | 0 | json j; |
371 | 0 | logoFHandle >> j; |
372 | 0 | std::vector<LogoOverlay> logoInput = j.get<std::vector<LogoOverlay>>(); |
373 | 0 | if( !logoInput.empty()) |
374 | 0 | m_cLogo = logoInput.at(0); |
375 | 0 | else |
376 | 0 | { |
377 | 0 | rcOstr << "error: logo json parsing error in file '" << fileName << "'." << std::endl; |
378 | 0 | return -1; |
379 | 0 | } |
380 | 0 | } |
381 | 0 | catch (std::exception& e) |
382 | 0 | { |
383 | 0 | rcOstr << "logo json parsing error: " << e.what() << "\n"; |
384 | 0 | return -1; |
385 | 0 | } |
386 | 0 | |
387 | 0 | if( m_cLogo.inputOpts.bgColorMin >= 0 && m_cLogo.inputOpts.bgColorMax < 0 ) |
388 | 0 | { |
389 | 0 | rcOstr << "logo must define range of BgColorMin/BgColorMax. set BgColorMin but missing BgColorMax (min/max " << |
390 | 0 | m_cLogo.inputOpts.bgColorMin << "/" << m_cLogo.inputOpts.bgColorMax << ")\n"; |
391 | 0 | return -1; |
392 | 0 | } |
393 | 0 | if( m_cLogo.inputOpts.bgColorMax >= 0 && m_cLogo.inputOpts.bgColorMin < 0 ) |
394 | 0 | { |
395 | 0 | rcOstr << "logo must define range of BgColorMin/BgColorMax. set BgColorMax but missing BgColorMin (min/max " << |
396 | 0 | m_cLogo.inputOpts.bgColorMin << "/" << m_cLogo.inputOpts.bgColorMax << ")\n"; |
397 | 0 | return -1; |
398 | 0 | } |
399 | 0 | if( m_cLogo.inputOpts.bgColorMin >= 0 && m_cLogo.inputOpts.bgColorMax < m_cLogo.inputOpts.bgColorMin ) |
400 | 0 | { |
401 | 0 | rcOstr << "logo must define range of BgColorMin/BgColorMax. BgColorMax must be >= BgColorMin (min/max " << |
402 | 0 | m_cLogo.inputOpts.bgColorMin << "/" << m_cLogo.inputOpts.bgColorMax << ")\n"; |
403 | 0 | return -1; |
404 | 0 | } |
405 | 0 |
|
406 | 0 | std::ifstream logofile(m_cLogo.inputOpts.logoFilename); |
407 | 0 | if ( ! logofile.is_open() ) |
408 | 0 | { |
409 | 0 | // check if logo path is relative from json file -> make it absolute |
410 | 0 | size_t pos = fileName.find_last_of("/\\"); |
411 | 0 | if ( pos != std::string::npos ) |
412 | 0 | { |
413 | 0 | std::string folder = fileName.substr(0,pos); |
414 | 0 | std::string absPath = folder + "/" + m_cLogo.inputOpts.logoFilename; |
415 | 0 | std::ifstream abslogofile(absPath); |
416 | 0 | if ( abslogofile.is_open() ) |
417 | 0 | { |
418 | 0 | m_cLogo.inputOpts.logoFilename = absPath; |
419 | 0 | logofile.swap( abslogofile ); |
420 | 0 | } |
421 | 0 | } |
422 | 0 |
|
423 | 0 | if ( ! logofile.is_open() ) |
424 | 0 | { |
425 | 0 | rcOstr << "Failed to open logo overlay file: " << m_cLogo.inputOpts.logoFilename << std::endl; |
426 | 0 | return -1; |
427 | 0 | } |
428 | 0 | } |
429 | 0 | |
430 | 0 | // limit opacity in range 0-100 % |
431 | 0 | m_cLogo.renderOpts.opacity = std::min( m_cLogo.renderOpts.opacity, 100 ); |
432 | 0 | m_cLogo.renderOpts.opacity = std::max( m_cLogo.renderOpts.opacity, 0 ); |
433 | 0 | |
434 | 0 | if( logoFHandle.is_open() ) |
435 | 0 | logoFHandle.close(); |
436 | 0 |
|
437 | 0 | return 0; |
438 | 0 | #else |
439 | 0 | rcOstr << "error: json not enabled - cannot read logo overlay file '" << fileName << "'." << std::endl; |
440 | 0 | return -1; |
441 | 0 | #endif |
442 | 0 | } |
443 | | |
444 | | int renderLogo ( const vvencYUVBuffer& yuvDestBuf ) |
445 | 0 | { |
446 | 0 | if( !m_bInitialized ) |
447 | 0 | { |
448 | 0 | return -1; |
449 | 0 | } |
450 | 0 |
|
451 | 0 | if( m_bBypass ){ return 0; } |
452 | 0 | |
453 | 0 | if ( m_cYuvBufLogo.planes[0].width > yuvDestBuf.planes[0].width || m_cYuvBufLogo.planes[0].height > yuvDestBuf.planes[0].height ) |
454 | 0 | { |
455 | 0 | return -1; |
456 | 0 | } |
457 | 0 | |
458 | 0 | int logoPosX = 0; |
459 | 0 | int logoPosY = 0; |
460 | 0 | if( m_cLogo.renderOpts.offsetHor != 0 ) |
461 | 0 | { |
462 | 0 | const int maxX = yuvDestBuf.planes[0].width - m_cYuvBufLogo.planes[0].width; |
463 | 0 | logoPosX = (m_cLogo.renderOpts.offsetHor >= 0) ? |
464 | 0 | std::min( maxX, m_cLogo.renderOpts.offsetHor ) : |
465 | 0 | std::max( 0, maxX + m_cLogo.renderOpts.offsetHor+1); |
466 | 0 | |
467 | 0 | } |
468 | 0 | |
469 | 0 | if( m_cLogo.renderOpts.offsetVer != 0 ) |
470 | 0 | { |
471 | 0 | const int maxY = yuvDestBuf.planes[0].height - m_cYuvBufLogo.planes[0].height; |
472 | 0 | logoPosY = (m_cLogo.renderOpts.offsetVer >= 0) ? |
473 | 0 | std::min( maxY, m_cLogo.renderOpts.offsetVer ) : |
474 | 0 | std::max( 0, maxY + m_cLogo.renderOpts.offsetVer+1); |
475 | 0 | } |
476 | 0 | |
477 | 0 | const int numComp = (m_chromaFormat==VVENC_CHROMA_400) ? 1 : 3; |
478 | 0 | for( int comp = 0; comp < numComp; comp++ ) |
479 | 0 | { |
480 | 0 | vvencYUVPlane yuvDes = yuvDestBuf.planes[ comp ]; |
481 | 0 | vvencYUVPlane yuvLogo = m_cYuvBufLogo.planes[ comp ]; |
482 | 0 | |
483 | 0 | const int csx = ( (comp == 0) || (m_chromaFormat==VVENC_CHROMA_444) ) ? 0 : 1; |
484 | 0 | const int csy = ( (comp == 0) || (m_chromaFormat!=VVENC_CHROMA_420) ) ? 0 : 1; |
485 | 0 | const int16_t* src = yuvLogo.ptr; |
486 | 0 | int16_t* dst = yuvDes.ptr + ( (logoPosY >> csy) * yuvDes.stride ) + (logoPosX >> csx); |
487 | 0 |
|
488 | 0 | if( m_bAlphaNeeded && m_cYuvBufAlpha.planes[0].ptr ) |
489 | 0 | { |
490 | 0 | vvencYUVPlane yuvAlpha = m_cYuvBufAlpha.planes[0]; |
491 | 0 | const int16_t* alpha = yuvAlpha.ptr; |
492 | 0 | |
493 | 0 | for( int y = 0; y < yuvLogo.height; y++ ) |
494 | 0 | { |
495 | 0 | int xA = 0; |
496 | 0 | for( int x = 0; x < yuvLogo.width; x++, xA += (1 << csx) ) |
497 | 0 | { |
498 | 0 | if( alpha[xA] >= 100 ) |
499 | 0 | { |
500 | 0 | dst[x] = src[x]; |
501 | 0 | } |
502 | 0 | else if( alpha[xA] > 0 ) |
503 | 0 | { |
504 | 0 | dst[x] = (( src[x] * alpha[xA] ) + ( dst[x] * (100-alpha[xA]) )) / 100 ; |
505 | 0 | } |
506 | 0 | } |
507 | 0 | src += yuvLogo.stride; |
508 | 0 | dst += yuvDes.stride; |
509 | 0 | alpha += ( (1 << csy) * yuvAlpha.stride); |
510 | 0 | } |
511 | 0 | } |
512 | 0 | else |
513 | 0 | { |
514 | 0 | for( int y = 0; y < yuvLogo.height; y++ ) |
515 | 0 | { |
516 | 0 | for( int x = 0; x < yuvLogo.width; x++ ) |
517 | 0 | { |
518 | 0 | dst[x] = src[x]; |
519 | 0 | } |
520 | 0 | src += yuvLogo.stride; |
521 | 0 | dst += yuvDes.stride; |
522 | 0 | } |
523 | 0 | } |
524 | 0 | } |
525 | 0 | |
526 | 0 | return 0; |
527 | 0 | } |
528 | | |
529 | | private: |
530 | | |
531 | | void initAlphaMask() |
532 | 0 | { |
533 | 0 | if( !m_bAlphaNeeded || m_bBypass ) return; |
534 | 0 | |
535 | 0 | // init alpha mask |
536 | 0 | bool bgColorSet = (m_cLogo.inputOpts.bgColorMin >= 0 && m_cLogo.inputOpts.bgColorMax >= 0 ) ? true : false; |
537 | 0 | bool opacitySet = (m_cLogo.renderOpts.opacity > 0) ? true : false; |
538 | 0 | int transp = 100 - m_cLogo.renderOpts.opacity; // transparency level |
539 | 0 |
|
540 | 0 | vvencYUVPlane yuvSrc = m_cYuvBufLogo.planes[0]; |
541 | 0 | vvencYUVPlane yuvDes = m_cYuvBufAlpha.planes[0]; |
542 | 0 | |
543 | 0 | const int16_t* src = yuvSrc.ptr; |
544 | 0 | int16_t* dst = yuvDes.ptr; |
545 | 0 | |
546 | 0 | for( int y = 0; y < yuvSrc.height; y++ ) |
547 | 0 | { |
548 | 0 | for( int x = 0; x < yuvSrc.width; x++ ) |
549 | 0 | { |
550 | 0 | if( bgColorSet && ( src[x] >= m_cLogo.inputOpts.bgColorMin && src[x] <= m_cLogo.inputOpts.bgColorMax )) |
551 | 0 | { |
552 | 0 | dst[x] = 0; // ignore background ( transparent ) |
553 | 0 | } |
554 | 0 | else if( opacitySet ) |
555 | 0 | { |
556 | 0 | dst[x] = transp; |
557 | 0 | } |
558 | 0 | else |
559 | 0 | { |
560 | 0 | dst[x] = 100; // opaque |
561 | 0 | } |
562 | 0 | } |
563 | 0 | src += yuvSrc.stride; |
564 | 0 | dst += yuvDes.stride; |
565 | 0 | } |
566 | 0 | } |
567 | | |
568 | | private: |
569 | | bool m_bInitialized = false; |
570 | | vvencChromaFormat m_chromaFormat = VVENC_NUM_CHROMA_FORMAT; |
571 | | LogoOverlay m_cLogo; |
572 | | vvencYUVBuffer m_cYuvBufLogo; |
573 | | vvencYUVBuffer m_cYuvBufAlpha; |
574 | | bool m_bAlphaNeeded = false; |
575 | | bool m_bBypass = false; |
576 | | }; |
577 | | |
578 | | } // namespace apputils |
579 | | |
580 | | //! \} |
581 | | |