<?php
/**
* SeekQuarry/Yioop --
* Open Source Pure PHP Search Engine, Crawler, and Indexer
*
* Copyright (C) 2009 - 2026 Chris Pollett chris@pollett.org
*
* LICENSE:
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* END LICENSE
*
* @author Chris Pollett chris@pollett.org
* @license https://www.gnu.org/licenses/ GPL3
* @link https://www.seekquarry.com/
* @copyright 2009 - 2026
* @filesource
*
* This class holds one decoded frame. It keeps a brightness plane and two
* color planes, and turns them into a picture a browser can show.
*/
namespace seekquarry\yioop\library\av_processing;
/**
* VideoPicture holds one decoded frame: a brightness plane and two color
* planes, with what it takes to turn them into a picture.
*/
class VideoPicture
{
/**
* __construct holds one decoded picture: a plane of brightness and two of
* color, with the sizes and offsets needed to read the part that is shown.
*
* @param array $luma brightness samples
* @param array $blue blue color difference samples
* @param array $red red color difference samples
* @param int $shown_width how wide, in pixels
* @param int $shown_height how tall, in pixels
* @param int $stride_y how many values one row of brightness takes
* @param int $stride_c how many values one row of color takes
* @param int $off_x how far across the picture the frame proper starts
* @param int $off_y how far down the picture the frame proper starts
* @param int $sub_x how much the color planes are shrunk across
* @param int $sub_y how much the color planes are shrunk down
*/
public function __construct(
/**
* $luma stores the brightness of every pixel, one value each, row after
* row. This is the plane a black and white thumbnail could be drawn
* from on its own.
* @var array
*/
public array $luma,
/**
* $blue stores how blue each pixel is, away from gray. There is one
* value for every few pixels rather than for each, which is what the
* shrink below describes.
* @var array
*/
public array $blue,
/**
* $red stores how red each pixel is, away from gray. There is
* one value for every few pixels rather than for each, since a
* picture's color is stored more coarsely than its brightness.
* @var array
*/
public array $red,
/**
* $shown_width stores how wide the picture is meant to be shown, which
* can be less than the planes hold, since a decoder works in whole
* blocks.
* @var int
*/
public int $shown_width,
/**
* $shown_height stores how tall the picture is meant to be
* shown, which can be less than the planes hold, since a
* decoder works in whole blocks.
* @var int
*/
public int $shown_height,
/**
* $stride_y stores how many values one row of brightness takes,
* counting any the decoder added to reach a whole block. Reading pixel
* by pixel steps by this rather than by the width.
* @var int
*/
public int $stride_y,
/**
* $stride_c stores how many values one row of a color plane takes,
* counting any the decoder added to reach a whole block. Same
* reason.
* @var int
*/
public int $stride_c,
/**
* $off_x stores how far across the planes the picture proper starts. A
* decoder may write a border it does not mean to show.
* @var int
*/
public int $off_x = 0,
/**
* $off_y stores how far down the planes the picture proper starts.
* A decoder may write a border it does not mean to show.
* @var int
*/
public int $off_y = 0,
/**
* $sub_x stores how many pixels across share one color value. One means
* every pixel has its own; two is what most video uses.
* @var int
*/
public int $sub_x = 1,
/**
* $sub_y stores how many pixels down share one color value.
* @var int
*/
public int $sub_y = 1
) {
}
/**
* toImage bT.601 limited-range YUV 4:2:0 to red, green and blue. Streams
* that signal a different matrix or full range will come out slightly off;
* video color metadata is not parsed.
*
* @return GdImage what was read
*/
public function toImage()
{
/*
The samples are turned into a bitmap in memory and handed to the
image library in one call. Setting each sample separately costs
a function call per sample, which for a large picture is most of
the work. A bitmap holds its rows bottom upward and its samples
blue first, so they are built in that order.
*/
$wide = $this->shown_width;
$tall = $this->shown_height;
$padding = (4 - (($wide * 3) % 4)) % 4;
$tail = str_repeat("\x00", $padding);
$pixels = "";
for ($line = $tall - 1; $line >= 0; $line--) {
$source = $line + $this->off_y;
$luma_row = $source * $this->stride_y;
$chroma_row = ($source >> $this->sub_y) * $this->stride_c;
$bytes = [];
for ($col = 0; $col < $wide; $col++) {
$across = $col + $this->off_x;
$chroma_col = $chroma_row + ($across >> $this->sub_x);
$luma = $this->luma[$luma_row + $across] - 16;
$blue = $this->blue[$chroma_col] - 128;
$red = $this->red[$chroma_col] - 128;
$scaled = 298 * $luma;
$value = ($scaled + 516 * $blue + 128) >> 8;
$bytes[] = $value < 0 ? 0 : ($value > 255 ? 255 : $value);
$value = ($scaled - 100 * $blue - 208 * $red + 128) >> 8;
$bytes[] = $value < 0 ? 0 : ($value > 255 ? 255 : $value);
$value = ($scaled + 409 * $red + 128) >> 8;
$bytes[] = $value < 0 ? 0 : ($value > 255 ? 255 : $value);
}
$pixels .= pack("C*", ...$bytes) . $tail;
}
$header = "BM" . pack("VvvV", 54 + strlen($pixels), 0, 0, 54)
. pack("VllvvVVllVV", 40, $wide, $tall, 1, 24, 0,
strlen($pixels), 2835, 2835, 0, 0);
$image = imagecreatefromstring($header . $pixels);
if ($image === false) {
throw new VideoException("could not build the picture");
}
return $image;
}
}