Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -2154,6 +2154,7 @@
'OC\\Preview\\Image' => $baseDir . '/lib/private/Preview/Image.php',
'OC\\Preview\\Imaginary' => $baseDir . '/lib/private/Preview/Imaginary.php',
'OC\\Preview\\ImaginaryPDF' => $baseDir . '/lib/private/Preview/ImaginaryPDF.php',
'OC\\Preview\\JP2' => $baseDir . '/lib/private/Preview/JP2.php',
'OC\\Preview\\JPEG' => $baseDir . '/lib/private/Preview/JPEG.php',
'OC\\Preview\\Krita' => $baseDir . '/lib/private/Preview/Krita.php',
'OC\\Preview\\MP3' => $baseDir . '/lib/private/Preview/MP3.php',
Expand Down
1 change: 1 addition & 0 deletions lib/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -2195,6 +2195,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\Preview\\Image' => __DIR__ . '/../../..' . '/lib/private/Preview/Image.php',
'OC\\Preview\\Imaginary' => __DIR__ . '/../../..' . '/lib/private/Preview/Imaginary.php',
'OC\\Preview\\ImaginaryPDF' => __DIR__ . '/../../..' . '/lib/private/Preview/ImaginaryPDF.php',
'OC\\Preview\\JP2' => __DIR__ . '/../../..' . '/lib/private/Preview/JP2.php',
'OC\\Preview\\JPEG' => __DIR__ . '/../../..' . '/lib/private/Preview/JPEG.php',
'OC\\Preview\\Krita' => __DIR__ . '/../../..' . '/lib/private/Preview/Krita.php',
'OC\\Preview\\MP3' => __DIR__ . '/../../..' . '/lib/private/Preview/MP3.php',
Expand Down
38 changes: 38 additions & 0 deletions lib/private/Preview/JP2.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/

namespace OC\Preview;

//.jp2
class JP2 extends Bitmap {
#[\Override]
public function getMimeType(): string {
return '/image\/jp2/';
}

#[\Override]
protected function getAllowedMimeTypes(): string {
return '/image\/jp2/';
}

#[\Override]
protected function getMagicStrings(): array {
return [
// The JP2 signature box, which opens the file: its length, the
// box type, and the four bytes that catch a transfer having
// mangled the line endings
"\x00\x00\x00\x0CjP \x0D\x0A\x87\x0A",
];
}

#[\Override]
protected function getImagickFormatHint(): string {
return 'jp2';
}
}
2 changes: 2 additions & 0 deletions lib/private/PreviewManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
use OC\Preview\IMagickSupport;
use OC\Preview\Imaginary;
use OC\Preview\ImaginaryPDF;
use OC\Preview\JP2;
use OC\Preview\JPEG;
use OC\Preview\Krita;
use OC\Preview\MarkDown;
Expand Down Expand Up @@ -332,6 +333,7 @@ protected function registerCoreProviders(): void {
$imagickProviders = [
'SVG' => ['mimetype' => '/image\/svg\+xml/', 'class' => SVG::class],
'TIFF' => ['mimetype' => '/image\/tiff/', 'class' => TIFF::class],
'JP2' => ['mimetype' => '/image\/jp2/', 'class' => JP2::class],
'PDF' => ['mimetype' => '/application\/pdf/', 'class' => PDF::class],
'AI' => ['mimetype' => '/application\/illustrator/', 'class' => Illustrator::class],
'PSD' => ['mimetype' => '/application\/x-photoshop/', 'class' => Photoshop::class],
Expand Down
2 changes: 1 addition & 1 deletion tests/data/REUSE.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ SPDX-FileCopyrightText = "2012 ownCloud, Inc."
SPDX-License-Identifier = "AGPL-3.0-only"

[[annotations]]
path = ["testavatar.png", "testimage.avif", "testimage.gif", "testimage.jpg", "testimage.png"]
path = ["testavatar.png", "testimage.avif", "testimage.gif", "testimage.jp2", "testimage.jpg", "testimage.png"]
precedence = "aggregate"
SPDX-FileCopyrightText = "2013 ownCloud, Inc."
SPDX-License-Identifier = "AGPL-3.0-only"
Expand Down
Binary file added tests/data/testimage.jp2
Binary file not shown.
2 changes: 1 addition & 1 deletion tests/lib/Preview/AVIFImagickTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
*/
#[\PHPUnit\Framework\Attributes\Group('DB')]
class AVIFImagickTest extends Provider {
use AvifPreviewTrait;
use PreviewPixelsTrait;

#[\Override]
protected function setUp(): void {
Expand Down
2 changes: 1 addition & 1 deletion tests/lib/Preview/AVIFTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
*/
#[\PHPUnit\Framework\Attributes\Group('DB')]
class AVIFTest extends Provider {
use AvifPreviewTrait;
use PreviewPixelsTrait;

#[\Override]
protected function setUp(): void {
Expand Down
54 changes: 54 additions & 0 deletions tests/lib/Preview/JP2Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace Test\Preview;

use OC\Preview\JP2;

/**
* Class JP2Test
*
* @package Test\Preview
*/
#[\PHPUnit\Framework\Attributes\Group('DB')]
class JP2Test extends Provider {
use PreviewPixelsTrait;

#[\Override]
protected function setUp(): void {
if (!extension_loaded('imagick')) {
$this->markTestSkipped('ImageMagick is not installed. Skipping tests');
}
if (!in_array('JP2', \Imagick::queryFormats('JP2'), true)) {
$this->markTestSkipped('ImageMagick was built without JPEG 2000. Skipping tests');
}

$fileName = 'testimage.jp2';
$sourcePath = \OC::$SERVERROOT . '/tests/data/' . $fileName;

// Reporting the coder is not the same as being able to use it: the
// OpenJPEG delegate may be missing, or policy.xml may have disabled
// it, in which case decoding throws and the tests would fail rather
// than skip. Decode once for real before committing to them.
try {
(new \Imagick())->readImage('jp2:' . $sourcePath . '[0]');
} catch (\ImagickException $e) {
$this->markTestSkipped('ImageMagick cannot decode JPEG 2000 here: ' . $e->getMessage() . '. Skipping tests');
}

parent::setUp();

$this->imgPath = $this->prepareTestFile($fileName, $sourcePath);
$this->width = 1680;
$this->height = 1050;
$this->provider = new JP2();
}

public function testPreviewCarriesThePicture(): void {
$this->assertPreviewShowsThePicture();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
* came back, which a blank canvas of the right shape would satisfy just as
* well as a decoded photo. These read the pixels.
*/
trait AvifPreviewTrait {
trait PreviewPixelsTrait {
/** How far the average may drift, over encoding and scaling */
private int $tolerance = 12;

Expand Down Expand Up @@ -66,9 +66,9 @@ protected function assertPreviewShowsThePicture(): void {
$this->assertLessThanOrEqual(256, $preview->width());
$this->assertLessThanOrEqual(256, $preview->height());

// The fixture is a re-encode of testimage.jpg, so the two hold the
// same picture: a strong magenta whose average survives both the
// encoding and the scaling. A blank or black canvas misses by ~250.
// Every fixture using this is a re-encode of testimage.jpg, so they
// hold the same picture: a strong magenta whose average survives
// the encoding and the scaling. A blank canvas misses by ~250.
$expected = $this->meanColour(file_get_contents(\OC::$SERVERROOT . '/tests/data/testimage.jpg'));
$actual = $this->meanColour($preview->data());

Expand Down
Loading