From 1552add4caaa5239e67d416dd75c4d7ca9085419 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Fri, 4 Dec 2020 04:28:51 +0100 Subject: [PATCH] Add integration tests for resized user avatars MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Even on solid color images the resizing can cause some small artifacts that slightly modify the color of certain pixels. Due to this now the color comparison is no longer strict but fuzzy. Signed-off-by: Daniel Calviño Sánchez --- build/integration/features/avatar.feature | 32 ++++++++++ .../integration/features/bootstrap/Avatar.php | 63 ++++++++++++++++--- 2 files changed, 88 insertions(+), 7 deletions(-) diff --git a/build/integration/features/avatar.feature b/build/integration/features/avatar.feature index 8580471ef5..579417844e 100644 --- a/build/integration/features/avatar.feature +++ b/build/integration/features/avatar.feature @@ -133,3 +133,35 @@ Feature: avatar | X-NC-IsCustomAvatar | 0 | And last avatar is a square of size 128 And last avatar is not a single color + + + + Scenario: get user avatar with a larger size than the original one + Given Logging in using web as "user0" + And logged in user posts temporary avatar from file "data/coloured-pattern.png" + And logged in user crops temporary avatar + | x | 384 | + | y | 256 | + | w | 128 | + | h | 128 | + When user "user0" gets avatar for user "user0" with size "192" + Then The following headers should be set + | Content-Type | image/png | + | X-NC-IsCustomAvatar | 1 | + And last avatar is a square of size 192 + And last avatar is a single "#FF0000" color + + Scenario: get user avatar with a smaller size than the original one + Given Logging in using web as "user0" + And logged in user posts temporary avatar from file "data/coloured-pattern.png" + And logged in user crops temporary avatar + | x | 384 | + | y | 256 | + | w | 128 | + | h | 128 | + When user "user0" gets avatar for user "user0" with size "96" + Then The following headers should be set + | Content-Type | image/png | + | X-NC-IsCustomAvatar | 1 | + And last avatar is a square of size 96 + And last avatar is a single "#FF0000" color diff --git a/build/integration/features/bootstrap/Avatar.php b/build/integration/features/bootstrap/Avatar.php index 215a3386ab..90cc36067b 100644 --- a/build/integration/features/bootstrap/Avatar.php +++ b/build/integration/features/bootstrap/Avatar.php @@ -179,19 +179,50 @@ trait Avatar { * @param string $size */ public function lastAvatarIsASingleColor(string $color) { - Assert::assertEquals($color, $this->getColorFromLastAvatar()); + $expectedColor = $this->hexStringToRgbColor($color); + $colorFromLastAvatar = $this->getColorFromLastAvatar(); + + Assert::assertTrue($this->isSameColor($expectedColor, $colorFromLastAvatar), + $this->rgbColorToHexString($colorFromLastAvatar) . ' does not match expected ' . $color); + } + + private function hexStringToRgbColor($hexString) { + // Strip initial "#" + $hexString = substr($hexString, 1); + + $rgbColorInt = hexdec($hexString); + + // RGBA hex strings are not supported; the given string is assumed to be + // an RGB hex string. + return [ + 'red' => ($rgbColorInt >> 16) & 0xFF, + 'green' => ($rgbColorInt >> 8) & 0xFF, + 'blue' => $rgbColorInt & 0xFF, + 'alpha' => 0 + ]; + } + + private function rgbColorToHexString($rgbColor) { + $rgbColorInt = ($rgbColor['red'] << 16) + ($rgbColor['green'] << 8) + ($rgbColor['blue']); + + return '#' . str_pad(strtoupper(dechex($rgbColorInt)), 6, '0', STR_PAD_LEFT); } private function getColorFromLastAvatar() { $image = imagecreatefromstring($this->lastAvatar); - $firstPixelColor = imagecolorat($image, 0, 0); + $firstPixelColorIndex = imagecolorat($image, 0, 0); + $firstPixelColor = imagecolorsforindex($image, $firstPixelColorIndex); for ($i = 0; $i < imagesx($image); $i++) { for ($j = 0; $j < imagesx($image); $j++) { - $currentPixelColor = imagecolorat($image, $i, $j); + $currentPixelColorIndex = imagecolorat($image, $i, $j); + $currentPixelColor = imagecolorsforindex($image, $currentPixelColorIndex); - if ($firstPixelColor !== $currentPixelColor) { + // The colors are compared with a small allowed delta, as even + // on solid color images the resizing can cause some small + // artifacts that slightly modify the color of certain pixels. + if (!$this->isSameColor($firstPixelColor, $currentPixelColor)) { imagedestroy($image); return null; @@ -201,8 +232,26 @@ trait Avatar { imagedestroy($image); - // Assume that the image is a truecolor image and thus the index is the - // RGB value of the pixel as an integer. - return '#' . str_pad(strtoupper(dechex($firstPixelColor)), 6, '0', STR_PAD_LEFT); + return $firstPixelColor; + } + + private function isSameColor(array $firstColor, array $secondColor, int $allowedDelta = 1) { + if ($this->isSameColorComponent($firstColor['red'], $secondColor['red'], $allowedDelta) && + $this->isSameColorComponent($firstColor['green'], $secondColor['green'], $allowedDelta) && + $this->isSameColorComponent($firstColor['blue'], $secondColor['blue'], $allowedDelta) && + $this->isSameColorComponent($firstColor['alpha'], $secondColor['alpha'], $allowedDelta)) { + return true; + } + + return false; + } + + private function isSameColorComponent(int $firstColorComponent, int $secondColorComponent, int $allowedDelta) { + if ($firstColorComponent >= ($secondColorComponent - $allowedDelta) && + $firstColorComponent <= ($secondColorComponent + $allowedDelta)) { + return true; + } + + return false; } }