1
0
Viktor Sokolov 2 долоо хоног өмнө
parent
commit
5f9f31c69e
29 өөрчлөгдсөн 113 нэмэгдсэн , 54 устгасан
  1. 3 0
      .devcontainer/Dockerfile
  2. 93 37
      integration/load_test.go
  3. 1 1
      processing/processing_test.go
  4. BIN
      testdata/test-hashes/TestIntegration/TestLoadSaveToPng/1-bpp.hash
  5. BIN
      testdata/test-hashes/TestIntegration/TestLoadSaveToPng/16-bpp.hash
  6. BIN
      testdata/test-hashes/TestIntegration/TestLoadSaveToPng/24-bpp-no-alpha-mask.hash
  7. BIN
      testdata/test-hashes/TestIntegration/TestLoadSaveToPng/24-bpp.hash
  8. BIN
      testdata/test-hashes/TestIntegration/TestLoadSaveToPng/32-bpp-with-alpha-self-gen.hash
  9. BIN
      testdata/test-hashes/TestIntegration/TestLoadSaveToPng/32-bpp-with-alpha.hash
  10. BIN
      testdata/test-hashes/TestIntegration/TestLoadSaveToPng/4-bpp.hash
  11. BIN
      testdata/test-hashes/TestIntegration/TestLoadSaveToPng/8-bpp-rle-move-to-x.hash
  12. BIN
      testdata/test-hashes/TestIntegration/TestLoadSaveToPng/8-bpp-rle-single-color.hash
  13. BIN
      testdata/test-hashes/TestIntegration/TestLoadSaveToPng/8-bpp-rle-small.hash
  14. BIN
      testdata/test-hashes/TestIntegration/TestLoadSaveToPng/8-bpp-rle.hash
  15. BIN
      testdata/test-hashes/TestIntegration/TestLoadSaveToPng/8-bpp.hash
  16. BIN
      testdata/test-hashes/TestIntegration/TestLoadSaveToPng/gif.hash
  17. BIN
      testdata/test-hashes/TestIntegration/TestLoadSaveToPng/heif.hash
  18. BIN
      testdata/test-hashes/TestIntegration/TestLoadSaveToPng/jpg.hash
  19. BIN
      testdata/test-hashes/TestIntegration/TestLoadSaveToPng/jxl.hash
  20. BIN
      testdata/test-hashes/TestIntegration/TestLoadSaveToPng/multi-bmp.hash
  21. BIN
      testdata/test-hashes/TestIntegration/TestLoadSaveToPng/multi-png.hash
  22. BIN
      testdata/test-hashes/TestIntegration/TestLoadSaveToPng/png-256x256.hash
  23. BIN
      testdata/test-hashes/TestIntegration/TestLoadSaveToPng/single-bmp.hash
  24. BIN
      testdata/test-hashes/TestIntegration/TestLoadSaveToPng/svg.hash
  25. BIN
      testdata/test-hashes/TestIntegration/TestLoadSaveToPng/tiff.hash
  26. BIN
      testdata/test-hashes/TestIntegration/TestLoadSaveToPng/webp.hash
  27. 13 13
      testutil/image_hash.c
  28. 2 2
      testutil/image_hash.go
  29. 1 1
      vips/bmpsave.c

+ 3 - 0
.devcontainer/Dockerfile

@@ -39,6 +39,9 @@ RUN go install github.com/air-verse/air@latest
 # Install lefthook
 RUN go install github.com/evilmartians/lefthook@latest
 
+# Install gotestsum
+RUN go install gotest.tools/gotestsum@latest
+
 # Install golangci-lint
 RUN curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.1.6
 

+ 93 - 37
integration/load_test.go

@@ -11,7 +11,7 @@ import (
 	"testing"
 	"unsafe"
 
-	"github.com/imgproxy/imgproxy/v3/config"
+	"github.com/corona10/goimagehash"
 	"github.com/imgproxy/imgproxy/v3/imagedata"
 	"github.com/imgproxy/imgproxy/v3/imagetype"
 	"github.com/imgproxy/imgproxy/v3/testutil"
@@ -20,21 +20,26 @@ import (
 )
 
 const (
-	similarityThreshold = 5 // Distance between images to be considered similar
+	maxDistance = 0 // maximum image distance
 )
 
 type LoadTestSuite struct {
 	Suite
 
-	testImagesPath string
+	testImagesPath      string
+	hashesPath          string
+	saveTmpImagesPath   string
+	createMissingHashes bool
 }
 
 func (s *LoadTestSuite) SetupTest() {
 	s.testImagesPath = s.TestData.Path("test-images")
+	s.hashesPath = s.TestData.Path("test-hashes")
+	s.saveTmpImagesPath = os.Getenv("TEST_SAVE_TMP_IMAGES")
+	s.createMissingHashes = len(os.Getenv("TEST_CREATE_MISSING_HASHES")) > 0
 
-	config.MaxAnimationFrames = 999
-	config.DevelopmentErrorsMode = true
-
+	s.Config().Security.DefaultOptions.MaxAnimationFrames = 999
+	s.Config().Server.DevelopmentErrorsMode = true
 	s.Config().Fetcher.Transport.Local.Root = s.testImagesPath
 }
 
@@ -55,48 +60,58 @@ func (s *LoadTestSuite) testLoadFolder(folder string) {
 		}
 
 		// get the base name of the file (8-bpp.png)
-		basePath := filepath.Base(path)
+		baseName := filepath.Base(path)
 
-		// Replace the extension with .png
-		referencePath := strings.TrimSuffix(basePath, filepath.Ext(basePath)) + ".png"
+		// Construct the source URL for imgproxy (no processing)
+		sourceUrl := fmt.Sprintf("/insecure/plain/local:///%s/%s@bmp", folder, baseName)
 
-		// Construct the full path to the reference image (integration/ folder)
-		referencePath = filepath.Join(s.testImagesPath, "integration", folder, referencePath)
+		// Read source image from imgproxy
+		sourceImageData := s.fetchImage(sourceUrl)
+		defer sourceImageData.Close()
 
-		// Construct the source URL for imgproxy (no processing)
-		sourceUrl := fmt.Sprintf("/insecure/plain/local:///%s/%s@png", folder, basePath)
+		// Save the source image if requested
+		s.saveTmpImage(folder, baseName, sourceImageData)
 
-		imgproxyImageData := s.fetchImage(sourceUrl)
-		var imgproxyImage vips.Image
-		s.Require().NoError(imgproxyImage.Load(imgproxyImageData, 1, 1.0, 1))
+		// Calculate image hash of the image returned by imgproxy
+		var sourceImage vips.Image
+		s.Require().NoError(sourceImage.Load(sourceImageData, 1, 1.0, 1))
+		defer sourceImage.Clear()
 
-		hash1, err := testutil.ImageHash(unsafe.Pointer(imgproxyImage.VipsImage))
+		sourceHash, err := testutil.ImageDifferenceHash(unsafe.Pointer(sourceImage.VipsImage))
 		s.Require().NoError(err)
 
-		referenceFile, err := os.Open(referencePath)
+		// Calculate image hash path (create folder if missing)
+		hashPath, err := s.makeTargetPath(s.hashesPath, s.T().Name(), baseName, "hash")
 		s.Require().NoError(err)
-		defer referenceFile.Close()
 
-		referenceImageData, err := s.Imgproxy().ImageDataFactory().NewFromPath(referencePath)
-		s.Require().NoError(err)
+		// Try to read or create the hash file
+		f, err := os.Open(hashPath)
+		if os.IsNotExist(err) {
+			// If the hash file does not exist, and we are not allowed to create it, fail
+			if !s.createMissingHashes {
+				s.Require().NoError(err, "failed to read target hash from %s, use TEST_CREATE_MISSING_HASHES=true to create it", hashPath)
+			}
 
-		var referenceImage vips.Image
-		s.Require().NoError(referenceImage.Load(referenceImageData, 1, 1.0, 1))
+			h, hashErr := os.Create(hashPath)
+			s.Require().NoError(hashErr, "failed to create target hash file %s", hashPath)
+			defer h.Close()
 
-		hash2, err := testutil.ImageHash(unsafe.Pointer(referenceImage.VipsImage))
-		s.Require().NoError(err)
+			hashErr = sourceHash.Dump(h)
+			s.Require().NoError(hashErr, "failed to write target hash to %s", hashPath)
 
-		distance, err := hash1.Distance(hash2)
-		s.Require().NoError(err)
+			s.T().Logf("Created missing hash in %s", hashPath)
+		} else {
+			// Otherwise, if there is no error or error is something else
+			s.Require().NoError(err)
+
+			targetHash, err := goimagehash.LoadImageHash(f)
+			s.Require().NoError(err, "failed to load target hash from %s", hashPath)
 
-		imgproxyImageData.Close()
-		referenceImageData.Close()
-		imgproxyImage.Clear()
-		referenceImage.Clear()
+			distance, err := sourceHash.Distance(targetHash)
+			s.Require().NoError(err, "failed to calculate hash distance for %s", baseName)
 
-		s.Require().LessOrEqual(distance, similarityThreshold,
-			"Image %s differs from reference image %s by %d, which is greater than the allowed threshold of %d",
-			basePath, referencePath, distance, similarityThreshold)
+			s.Require().LessOrEqual(distance, maxDistance, "image hashes are too different for %s: distance %d", baseName, distance)
+		}
 
 		return nil
 	})
@@ -109,17 +124,58 @@ func (s *LoadTestSuite) fetchImage(path string) imagedata.ImageData {
 	resp := s.GET(path)
 	defer resp.Body.Close()
 
-	s.Require().Equal(http.StatusOK, resp.StatusCode, "Expected status code 200 OK, got %d, path: %s", resp.StatusCode, path)
+	s.Require().Equal(http.StatusOK, resp.StatusCode, "expected status code 200 OK, got %d, path: %s", resp.StatusCode, path)
 
 	bytes, err := io.ReadAll(resp.Body)
-	s.Require().NoError(err, "Failed to read response body from %s", path)
+	s.Require().NoError(err, "failed to read response body from %s", path)
 
 	d, err := s.Imgproxy().ImageDataFactory().NewFromBytes(bytes)
-	s.Require().NoError(err, "Failed to load image from bytes for %s", path)
+	s.Require().NoError(err, "failed to load image from bytes for %s", path)
 
 	return d
 }
 
+// makeTargetPath creates the target directory and returns file path for saving
+// the image or hash.
+func (s *LoadTestSuite) makeTargetPath(base, folder, filename, ext string) (string, error) {
+	// Create the target directory if it doesn't exist
+	targetDir := path.Join(base, folder)
+	err := os.MkdirAll(targetDir, 0755)
+	s.Require().NoError(err, "failed to create %s target directory", targetDir)
+
+	// Replace the extension with the detected one
+	filename = strings.TrimSuffix(filename, filepath.Ext(filename)) + "." + ext
+
+	// Create the target file
+	targetPath := path.Join(targetDir, filename)
+
+	return targetPath, nil
+}
+
+// saveTmpImage saves the provided image data to a temporary file
+func (s *LoadTestSuite) saveTmpImage(folder, filename string, imageData imagedata.ImageData) {
+	if s.saveTmpImagesPath == "" {
+		return
+	}
+
+	// Detect the image type to get the correct extension
+	ext, err := imagetype.Detect(imageData.Reader())
+	s.Require().NoError(err)
+
+	targetPath, err := s.makeTargetPath(s.saveTmpImagesPath, folder, filename, ext.String())
+	s.Require().NoError(err, "failed to create TEST_SAVE_TMP_IMAGES target path for %s/%s", folder, filename)
+
+	targetFile, err := os.Create(targetPath)
+	s.Require().NoError(err, "failed to create TEST_SAVE_TMP_IMAGES target file %s", targetPath)
+	defer targetFile.Close()
+
+	// Write the image data to the file
+	_, err = io.Copy(targetFile, imageData.Reader())
+	s.Require().NoError(err, "failed to write to TEST_SAVE_TMP_IMAGES target file %s", targetPath)
+
+	s.T().Logf("Saved temporary image to %s", targetPath)
+}
+
 // TestLoadSaveToPng ensures that our load pipeline works,
 // including standard and custom loaders. For each source image
 // in the folder, it does the passthrough request through imgproxy:

+ 1 - 1
processing/processing_test.go

@@ -255,7 +255,7 @@ func (s *ProcessingTestSuite) TestResizeToFitExtendAR() {
 
 			var i vips.Image
 			s.Require().NoError(i.Load(result.OutData, 1, 1.0, 1))
-			h, err := testutil.ImageHash(unsafe.Pointer(i.VipsImage))
+			h, err := testutil.ImageDifferenceHash(unsafe.Pointer(i.VipsImage))
 			s.Require().NoError(err)
 			fmt.Println(h.ToString())
 		})

BIN
testdata/test-hashes/TestIntegration/TestLoadSaveToPng/1-bpp.hash


BIN
testdata/test-hashes/TestIntegration/TestLoadSaveToPng/16-bpp.hash


BIN
testdata/test-hashes/TestIntegration/TestLoadSaveToPng/24-bpp-no-alpha-mask.hash


BIN
testdata/test-hashes/TestIntegration/TestLoadSaveToPng/24-bpp.hash


BIN
testdata/test-hashes/TestIntegration/TestLoadSaveToPng/32-bpp-with-alpha-self-gen.hash


BIN
testdata/test-hashes/TestIntegration/TestLoadSaveToPng/32-bpp-with-alpha.hash


BIN
testdata/test-hashes/TestIntegration/TestLoadSaveToPng/4-bpp.hash


BIN
testdata/test-hashes/TestIntegration/TestLoadSaveToPng/8-bpp-rle-move-to-x.hash


BIN
testdata/test-hashes/TestIntegration/TestLoadSaveToPng/8-bpp-rle-single-color.hash


BIN
testdata/test-hashes/TestIntegration/TestLoadSaveToPng/8-bpp-rle-small.hash


BIN
testdata/test-hashes/TestIntegration/TestLoadSaveToPng/8-bpp-rle.hash


BIN
testdata/test-hashes/TestIntegration/TestLoadSaveToPng/8-bpp.hash


BIN
testdata/test-hashes/TestIntegration/TestLoadSaveToPng/gif.hash


BIN
testdata/test-hashes/TestIntegration/TestLoadSaveToPng/heif.hash


BIN
testdata/test-hashes/TestIntegration/TestLoadSaveToPng/jpg.hash


BIN
testdata/test-hashes/TestIntegration/TestLoadSaveToPng/jxl.hash


BIN
testdata/test-hashes/TestIntegration/TestLoadSaveToPng/multi-bmp.hash


BIN
testdata/test-hashes/TestIntegration/TestLoadSaveToPng/multi-png.hash


BIN
testdata/test-hashes/TestIntegration/TestLoadSaveToPng/png-256x256.hash


BIN
testdata/test-hashes/TestIntegration/TestLoadSaveToPng/single-bmp.hash


BIN
testdata/test-hashes/TestIntegration/TestLoadSaveToPng/svg.hash


BIN
testdata/test-hashes/TestIntegration/TestLoadSaveToPng/tiff.hash


BIN
testdata/test-hashes/TestIntegration/TestLoadSaveToPng/webp.hash


+ 13 - 13
testutil/image_hash.c

@@ -21,36 +21,36 @@ vips_image_read_to_memory(VipsImage *in, void **buf, size_t *size)
     return -1;
   }
 
+  VipsImage *base = vips_image_new();
+  VipsImage **t = (VipsImage **) vips_object_local_array(VIPS_OBJECT(base), 2);
+
   // Initialize output parameters
   *buf = NULL;
   *size = 0;
 
   // Convert to sRGB colorspace first if needed
-  if (vips_colourspace(in, &rgba_image, VIPS_INTERPRETATION_sRGB, NULL) != 0) {
+  if (vips_colourspace(in, &t[0], VIPS_INTERPRETATION_sRGB, NULL) != 0) {
+    VIPS_UNREF(base);
     vips_error("vips_image_read_to_memory", "failed to convert to sRGB");
     return -1;
   }
 
+  in = t[0];
+
   // Add alpha channel if not present (convert to RGBA)
-  VipsImage *with_alpha = NULL;
-  if (vips_image_hasalpha(rgba_image)) {
-    // Already has alpha, just reference it
-    with_alpha = rgba_image;
-    g_object_ref(with_alpha);
-  }
-  else {
+  if (!vips_image_hasalpha(in)) {
     // Add alpha channel
-    if (vips_addalpha(rgba_image, &with_alpha, NULL) != 0) {
-      g_object_unref(rgba_image);
+    if (vips_addalpha(in, &t[1], NULL) != 0) {
+      VIPS_UNREF(base);
       vips_error("vips_image_read_to_memory", "failed to add alpha channel");
       return -1;
     }
+    in = t[1];
   }
-  g_object_unref(rgba_image);
 
   // Get raw pixel data
-  *buf = vips_image_write_to_memory(with_alpha, size);
-  g_object_unref(with_alpha);
+  *buf = vips_image_write_to_memory(in, size);
+  VIPS_UNREF(base);
 
   if (*buf == NULL) {
     vips_error("vips_image_read_to_memory", "failed to write image to memory");

+ 2 - 2
testutil/image_hash.go

@@ -16,8 +16,8 @@ import (
 	"github.com/corona10/goimagehash"
 )
 
-// ImageHash calculates a hash of the VipsImage
-func ImageHash(vipsImgPtr unsafe.Pointer) (*goimagehash.ImageHash, error) {
+// ImageDifferenceHash calculates a hash of the VipsImage
+func ImageDifferenceHash(vipsImgPtr unsafe.Pointer) (*goimagehash.ImageHash, error) {
 	vipsImg := (*C.VipsImage)(vipsImgPtr)
 
 	// Convert to RGBA and read into memory using VIPS

+ 1 - 1
vips/bmpsave.c

@@ -124,7 +124,7 @@ vips_foreign_save_bmp_build(VipsObject *object)
   // bands (3 or 4) * 8 bits
   int bands = vips_image_get_bands(in);
 
-  if ((bands > 3) || (bands > 4)) {
+  if ((bands < 3) || (bands > 4)) {
     vips_error("vips_foreign_save_bmp_build", "BMP source file must have 3 or 4 bands (RGB or RGBA)");
     return -1;
   }