ama-team/projection-framework

0.2.1 2018-04-03 16:47 UTC

README

Packagist CircleCI branch Coveralls Scrutinizer Code Climate

不要相信覆盖率,它在撒谎

这是一个为常见的球面投影工作而创建的简单库 - 在编写本文时,用于将等经线投影和立方体贴图投影类型相互转换。

安装

composer require ama-team/image-projection-framework

需要PHP 5.6+和GD / Imagick。所有文件操作都通过league/flysystem完成,该库被明确设置为依赖项。

用法

六十秒启动

use \AmaTeam\Image\Projection\Framework;
use \AmaTeam\Image\Projection\Framework\EncodingOptions;
use \AmaTeam\Image\Projection\Specification;
use \AmaTeam\Image\Projection\Image\Format;

$framework = new Framework();
$source = new Specification('equirect', 'tmp/uploads/source.jpg');
$target = new Specification(
    'cube',
    'static/pano/{f}/{x}/{y}.jpg',
    new Box(512, 512) // tile size,
    new Box(2, 2) // layout, amount of tiles horizontally and vertically
);

$options = (new EncodingOptions())->setQuality(0.9);

$framework->convert($source, $target, Format::JPEG, $options);

您需要一个Framework实例来开始,它将使用cwd()作为fs根。接下来,Framework具有#convert()#convertAll()方法,可以将一个规范转换为其他(其他)规范,可以指定格式和编码选项(别担心,默认已经是jpeg / 90%)。规范基本上是全景的描述:它的类型、位置、面大小和瓦片大小。位置允许几个占位符 - {f}或{face}、{x}和{y}以自动识别/用瓦片参数填充它。对于等经线图像,您根本不需要它们,而对于立方体贴图的各个面,则使用它们的第一个字母命名 - u(p)、f(ront)、b(ack)、l(eft)、r(ight)、d(own)。类型是通过不区分大小写的前缀搜索找到的,所以EQUIRECTANGULARequirect基本上是相同的。

您很可能会将等经线投影创建为多分辨率立方体贴图,为了减少资源消耗,源投影只需读取一次 - 这可以通过使用#convertAll()轻松完成,它使用单个读取器

$targets = array_map(function ($index) {
    $side = 256 * (int) pow($index, 2);
    $tileSide = min($side, 512);
    $size = $side / $side;
    $tileSize = new Box($tileSide, $tileSide);
    $layout = new Box($size, $size);
    $path = sprintf('static/pano/%d/{f}/{x}/{y}.jpg', $index);
    return new Specification('cube', $path, $tileSize, $layout);
}, range(0, 3));

$framework->convertAll($source, $targets);

这应该可以解决这个问题。

最后要说的:它非常慢。当前的处理模型是按像素填充目标投影,所以需要一段时间(在我的笔记本电脑上可能需要一分钟,便宜的服务器可能会慢得多)。是的,它会尽可能多地烧掉一个核心。此外,别忘了源投影必须适合内存,不要因为您的图像太大而责怪我。

约定

投影表示为三级深的瓦片结构。每个投影由任意数量的面组成(立方体贴图为六个,等经线和一个地球面),每个面是一个二维网格的瓦片。瓦片位置,通过{face, x, y}确定,通过花括号占位符表达,如主用法示例中所示。所有投影都遵循该规则(尽管等经线不太可能有多个瓦片),但在未来的版本中可能会改变。

框架假定所有瓦片具有确切的大小。不遵循此规则可能导致未定义的行为和错误。

使用Flysystem假定所有路径都使用斜杠(不是反斜杠)指定,并且没有绝对路径,所有路径都是相对于根的。

考虑事项

此库通过从源投影创建虚拟球面访问器,然后使用它逐texel填充目标投影来处理图像。这引发了以下问题

  • 要完成目标投影,源投影必须完全加载到RAM中。对于大型投影,这可能会相当多,该库本身不执行任何大小验证,所以您必须自己监控RAM。此外,图像加载本身就增加了时间惩罚。
  • 主要的工作负载是一个巨大的循环,它要求获取特定球面坐标的颜色,然后用这种颜色填充目标图像。这会导致 {像素数量} 次函数调用,这个数字通常非常大,即使是现代处理器——生成 2048x2048 的立方体贴图需要填充 2400 万个像素,所有这些工作都在单个 CPU 核心上完成,没有多少开销可以优化。 这非常慢,几乎无能为力。生成这种投影需要两分钟是正常的,预计一个核心占用 100% 也是预期的。你能做的只是通过过滤应该生成哪些瓦片来在多个核心上并行化工作;然而,这需要为每个核心加载源投影到 RAM 中。

如果你想更快地转换投影或利用 GPU,你将需要自己实现相同的功能,最有可能使用其他语言。图像处理始终是一个复杂的事情,PHP 并不足以有效地解决这个问题。

过滤生成的瓦片,添加处理器和监听器

上述所有内容都需要更多细致的工作和其他方法。《框架》实例提供了 getConverter() 方法,它允许使用 createConversion()createConversions() 方法进行更详细的工作。这些将创建一个准备执行转换的对象,但尚未启动(通过 #run() 方法启动),并公开了额外的处理能力。

如果你考虑通过安排不同的瓦片到不同的工作者来并行化工作,你需要限制某些瓦片在特定工作者上创建。过滤功能正是为此而存在的。

// only specified faces will be created
$filter = new FaceFilter('f', 'b');
$options = (new ConversionOptions())->setFilters([$filter]);
$framework
    ->getConverter()
    ->createConversion($source, $target, $options)
    ->run();

如果你需要添加某种水印或应用自定义抗锯齿,你可以在瓦片生成后通过处理器来实现。

$fxaaProcessor = new FXAAProcessor();
$watermarkProcessor = new WatermarkProcessor();
$conversion = $framework
    ->getConverter()
    ->createConversion($source, $target);
    // 50 is order in which processor will run, so it will run before
    // watermark processor
    ->addProcessor(50, $fxaaProcessor)
    ->addProcessor(99, $watermarkProcessor);
    ->run();

最后要提到的是监听器——它们只是接受完整生成的瓦片的副作用生成器。最常见的一个例子是 SaveListener,它默认不包括在转换中。

$filesystem = $framework->getRegistry()->getFilesystem();
$listener = new SaveListener($filesystem, Format::JPEG);
$conversion = $framework
    ->getConverter()
    ->createConversion($source, $target)
    ->addListener($listener)
    ->run();

这就是六十秒示例中默认方法实际上所做的事情。

添加自定义投影类型

所有投影都非常相似:它们是一组图像,有一些规则将球面映射到它们上,反之亦然。你实际上需要实现 MappingInterface,将其嵌入到 AbstractHandler 子类中,并在框架中注册。

$framework = new Framework();
$framework->register('LittlePlanet', new LittlePlanetHandler()); 

你已经准备好了。

贡献

请随意进行分叉并将 PR 发送到 dev 分支。

测试

测试使用 Codeception 完成,除了目录结构和可安装的固件之外,一切都是相当常规的。

要安装固件,只需运行 test:setup 任务。

bin/robo test:setup

这将下载一些来自 flickr 的公共许可证测试图像,并将它们放入 tests/Data/External/Projections 中,同时将它们裁剪成面。

你可以使用 testtest:<suite> 命令运行测试。

bin/robo test
bin/robo test:acceptance
bin/robo test:unit --coverage

验收测试正在进行一些实际的工作,处理数百万个纹理,所以你绝对不应该打开覆盖率,除非你是 Intel 压力测试团队的一员。它们已经足够慢了,相信我。

以下命令将生成覆盖率报告和 Allure 报告。

bin/robo test:report

dev 分支状态

CircleCI Coveralls Scrutinizer

许可证

MIT 许可证

AMA 团队,2017