內容目錄

以下是你提供的模組名稱的中文解釋,假設這是一個影像處理相關的軟體架構:

📦 模組名稱與中文說明

模組名稱 中文說明
DoubleMatrix 雙精度矩陣處理模組,用於影像濾波、傅立葉轉換等數值運算
Image 影像資料結構,可能包含像素陣列、解析度、色彩格式等資訊
Rectangle 矩形區域定義,用於標記ROI(感興趣區域)、裁剪範圍或物件邊界
DFTOutput 傅立葉轉換結果儲存模組,可能包含頻譜的幅值、相位等
ImageProcessing 影像處理核心模組,執行濾波、邊緣偵測、銳化等演算法
ImageTransformer 影像幾何或色彩轉換模組,例如旋轉、縮放、灰階轉換等
Task1test 任務一的測試模組,用於驗證特定演算法或功能是否正確
Task2test 任務二的測試模組,可能針對傅立葉轉換或影像轉換進行驗證

這樣的架構看起來非常模組化,適合進行影像處理的教學、展示或工程專案。如果你正在設計一個展示用的系統,我可以幫你補上 README 文件、模擬腳本、或是中英文註解的程式碼。你想針對哪個模組深入整理或優化呢?

1 DoubleMatrix

DoubleMatrix

這段 Java 程式碼定義了一個不可變的 DoubleMatrix 類別,用來表示雙精度浮點數的矩陣。以下是中文解釋與結構分析:

📘 類別說明:DoubleMatrix

這是一個用來表示 double 類型的二維矩陣的資料結構,設計上強調:

  • 不可變性final 類別)

  • 數值精度比較(使用 epsilon = 1e-7

  • 資料完整性保證(透過表示不變式)

    🧩 類別成員與功能

    成員/方法 中文說明
    epsilon 比較兩個矩陣是否相等時使用的誤差容忍值(10⁻⁷)
    matrix 儲存矩陣資料的二維陣列
    columns / rows 矩陣的欄數與列數(不可變)
    DoubleMatrix(double[][] _input) 建構子,從輸入的二維陣列建立矩陣,並進行深拷貝
    get(int row, int col) 取得指定位置的元素值
    getRows() / getColumns() 取得矩陣的列數與欄數
    equals(Object o) 比較兩個矩陣是否相等,允許微小誤差
    hashCode() 根據矩陣大小產生雜湊碼(簡單乘積)

    🔒 表示不變式(Representation Invariant)

    這些條件保證矩陣的結構正確:

  • rows >= 1columns >= 1

  • matrix.length == rows

  • matrix[0].length == columns

    ✅ 設計優點

  • 使用 Arrays.stream(...).map(...).toArray(...) 進行深拷貝,避免外部修改原始陣列。

  • 明確的錯誤處理(例如 null 或空陣列檢查)。

  • 支援矩陣相等比較,考慮浮點誤差。

    如果你想進一步擴充這個類別,例如加入矩陣加法、轉置、乘法或是支援 DFT 輸出格式,我可以幫你設計模組化的介面與測試案例。是否要我幫你加上中文註解版本或撰寫 README 文件來教學使用這個類別?

package ca.ubc.ece.cpen221.ip.core;

import java.util.Arrays;

/**
* This datatype represents a matrix of <code>double</code> values.
* <p>
* The constant fields <code>width</code> and <code>height</code> provide
* the dimensions of the matrix, and it is possible to compare two DoubleMatrices for
* equality using a set <code>epsilon</code> value of 10^(-7).
*/
public final class DoubleMatrix {
   public static final double epsilon = 1e-7;
   private double[][] matrix;
   public final int columns;
   public final int rows;

   /*
       Abstraction Function:
           DoubleMatrix represents a matrix of double-precision values.
           The matrix has dimensions height * width.

       Representation Invariant:
           rows >= 1
           columns >= 1
           matrix.length == rows
           matrix[0].length == columns
    */

   /**
    * Create an instance of DoubleMatrix from a two-dimensional array.
    *
    * @param _input is not null, and _input has at least one row and at least one column.
    */
   public DoubleMatrix(double[][] _input) {
       if (_input == null) {
           throw new IllegalArgumentException("input cannot be null");
       }

       rows = _input.length;
       if (rows == 0) {
           throw new IllegalArgumentException("matrix has to have at least one row");
       }

       columns = _input[0].length;
       if (columns == 0) {
           throw new IllegalArgumentException("matrix has to have at least one column");
       }

       matrix = Arrays.stream(_input).map(double[]::clone).toArray(double[][]::new);
   }

   /**
    * Return the entry at (row, col)
    * @param row the row of the entry to return, 0 <= row < rows
    * @param col the column of the entry to return, 0 <= col < columns
    * @return the entry at location (row, col)
    */
   public double get(int row, int col) {
       return matrix[row][col];
   }

   /**
    * Obtain the number of rows in the matrix
    * @return the number of rows in the matrix
    */
   public int getRows() {
       return rows;
   }

   /**
    * Obtain the number of columns in the matrix
    * @return the number of columns in the matrix
    */
   public int getColumns() {
       return columns;
   }

   @Override
   public boolean equals(Object o) {
       if (!(o instanceof DoubleMatrix)) {
           return false;
       }
       DoubleMatrix other = (DoubleMatrix) o;
       if (columns != other.columns || rows != other.rows) {
           return false;
       }
       for (int i = 0; i < rows; i++) {
           for (int j = 0; j < columns; j++) {
               if (Math.abs(matrix[i][j] - other.matrix[i][j]) > epsilon) {
                   return false;
               }
           }
       }
       return true;
   }

   @Override
   public int hashCode() {
       return columns * rows;
   }
}

2 Image

🖼 類別功能說明(中文)

這個類別提供操作影像中「單一像素」的方法,使用 RGB 色彩格式(不支援透明度 alpha 通道)。

📌 功能摘要:

  • 支援的影像格式:可讀取 PNG、GIF、JPEG 檔案,也可建立指定尺寸的空白影像。
  • 像素座標系統
    • 預設原點為左上角 (0, 0),符合影像處理常見慣例。
    • 可透過 setOriginLowerLeft() 方法將原點改為左下角。
  • 像素操作方式
    • 使用 get() / set() 方法搭配 Color 物件來讀取或設定像素顏色。
    • 使用 getRGB() / setRGB() 方法以 32 位元 int 編碼 RGB 色彩,避免建立暫時的 Color 物件。

🎨 RGB 編碼與解碼:

  • 解碼 RGB:

    java

    int r = (rgb >> 16) & 0xFF;
    int g = (rgb >> 8) & 0xFF;
    int b = (rgb >> 0) & 0xFF;
  • 編碼 RGB:

    java

    int rgb = (r << 16) + (g << 8) + (b << 0);

🧠 記憶體使用量:

  • 一張 W × H 的影像約使用 4 × W × H 位元組(每個像素佔 32 位元,即 4 bytes)。

這類設計非常適合用於影像處理教學、像素級操作、或是自訂濾鏡開發。如果你需要幫忙撰寫這個類別的中文註解版本、測試案例、或是整合進你的模組化系統,我可以幫你補齊。是否要我幫你整理成 showcase-ready 的教學模組?

package ca.ubc.ece.cpen221.ip.core;
/******************************************************************************
*  Data type for manipulating individual pixels of an image. The original
*  image can be read from a file in JPG, GIF, or PNG format, or the
*  user can create a blank image of a given dimension. Includes methods for
*  displaying the image in a window on the screen or saving to a file.
*
*  Remarks
*  -------
*   - pixel (x, y) is column x and row y, where (0, 0) is upper left
*
******************************************************************************/

import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import java.awt.Color;
import java.awt.FileDialog;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.URL;

/**
* This class provides methods for manipulating individual pixels of
* an image using the RGB color format. The alpha component (for transparency)
* is not currently supported.
* The original image can be read from a {@code PNG}, {@code GIF},
* or {@code JPEG} file or the user can create a blank image of a given dimension.
* This class includes methods for displaying the image in a window on
* the screen or saving it to a file.
* <p>
* Pixel (<em>col</em>, <em>row</em>) is column <em>col</em> and row <em>row</em>.
* By default, the origin (0, 0) is the pixel in the top-left corner,
* which is a common convention in image processing.
* The method {@link #setOriginLowerLeft()} changes the origin to the lower left.
* <p>
* The {@code get()} and {@code set()} methods use {@link Color} objects to get
* or set the color of the specified pixel.
* The {@code getRGB()} and {@code setRGB()} methods use a 32-bit {@code int}
* to encode the color, thereby avoiding the need to create temporary
* {@code Color} objects. The red (R), green (G), and blue (B) components
* are encoded using the least significant 24 bits.
* Given a 32-bit {@code int} encoding the color, the following code extracts
* the RGB components:
* <blockquote><pre>
*  int r = (rgb >> 16) & 0xFF;
*  int g = (rgb >>  8) & 0xFF;
*  int b = (rgb >>  0) & 0xFF;
*  </pre></blockquote>
* Given the RGB components (8-bits each) of a color,
* the following statement packs it into a 32-bit {@code int}:
* <blockquote><pre>
*  int rgb = (r << 16) + (g << 8) + (b << 0);
* </pre></blockquote>
* <p>
* A <em>W</em>-by-<em>H</em> image uses ~ 4 <em>W H</em> bytes of memory,
* since the color of each pixel is encoded as a 32-bit <code>int</code>.
* <p>
*/

public final class Image implements ActionListener {
   private final int width, height;           // width and height
   private BufferedImage image;               // the rasterized image
   private JFrame frame;                      // on-screen view
   private String filename;                   // name of file
   private boolean isOriginUpperLeft = true;  // location of origin

   /**
    * Creates a {@code width}-by-{@code height} image, with {@code width} columns
    * and {@code height} rows, where each pixel is black.
    *
    * @param width  the width of the image
    * @param height the height of the image
    * @throws IllegalArgumentException if {@code width} is negative or zero
    * @throws IllegalArgumentException if {@code height} is negative or zero
    */
   public Image(int width, int height) {
       if (width <= 0) {
           throw new IllegalArgumentException("width must be positive");
       }
       if (height <= 0) {
           throw new IllegalArgumentException("height must be positive");
       }
       this.width = width;
       this.height = height;
       image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
       // set to TYPE_INT_ARGB here and in next constructor to support transparency
   }

   /**
    * Creates a new image that is a deep copy of the argument image.
    *
    * @param image the image to copy
    * @throws IllegalArgumentException if {@code image} is {@code null}
    */
   public Image(Image image) {
       if (image == null) {
           throw new IllegalArgumentException("constructor argument is null");
       }

       width = image.width();
       height = image.height();
       this.image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
       filename = image.filename;
       isOriginUpperLeft = image.isOriginUpperLeft;
       for (int col = 0; col < width(); col++) {
           for (int row = 0; row < height(); row++) {
               this.image.setRGB(col, row, image.image.getRGB(col, row));
           }
       }
   }

   /**
    * Creates a image by reading an image from a file or URL.
    *
    * @param name the name of the file (.png, .gif, or .jpg) or URL.
    * @throws IllegalArgumentException if cannot read image
    * @throws IllegalArgumentException if {@code name} is {@code null}
    */
   public Image(String name) {
       if (name == null) {
           throw new IllegalArgumentException("constructor argument is null");
       }

       this.filename = name;
       try {
           // try to read from file in working directory
           File file = new File(name);
           if (file.isFile()) {
               image = ImageIO.read(file);
           } else {

               // resource relative to .class file
               URL url = getClass().getResource(filename);

               // resource relative to classloader root
               if (url == null) {
                   url = getClass().getClassLoader().getResource(name);
               }

               // or URL from web
               if (url == null) {
                   url = new URL(name);
               }

               image = ImageIO.read(url);
           }

           if (image == null) {
               throw new IllegalArgumentException("could not read image: " + name);
           }

           width = image.getWidth(null);
           height = image.getHeight(null);
       }
       catch (IOException ioe) {
           throw new IllegalArgumentException("could not open image: " + name, ioe);
       }
   }

   /**
    * Creates a image by reading the image from a PNG, GIF, or JPEG file.
    *
    * @param file the file
    * @throws IllegalArgumentException if cannot read image
    * @throws IllegalArgumentException if {@code file} is {@code null}
    */
   public Image(File file) {
       if (file == null) {
           throw new IllegalArgumentException("constructor argument is null");
       }

       try {
           image = ImageIO.read(file);
       }
       catch (IOException ioe) {
           throw new IllegalArgumentException("could not open file: " + file, ioe);
       }
       if (image == null) {
           throw new IllegalArgumentException("could not read file: " + file);
       }
       width = image.getWidth(null);
       height = image.getHeight(null);
       filename = file.getName();
   }

   /**
    * Returns the monochrome luminance of the given color as an intensity
    * between 0.0 and 255.0 using the NTSC formula
    * Y = 0.299*r + 0.587*g + 0.114*b. If the given color is a shade of gray
    * (r = g = b), this method is guaranteed to return the exact grayscale
    * value (an integer with no floating-point roundoff error).
    *
    * @param color the color to convert
    * @return the monochrome luminance (between 0.0 and 255.0)
    */
   public static double intensity(Color color) {
       int r = color.getRed();
       int g = color.getGreen();
       int b = color.getBlue();
       if (r == g && r == b) {
           return r;   // to avoid floating-point issues
       }
       return 0.299 * r + 0.587 * g + 0.114 * b;
   }

   /**
    * Returns a grayscale version of the given color as a {@code Color} object.
    *
    * @param color the {@code Color} object to convert to grayscale
    * @return a grayscale version of {@code color}
    */
   public static Color toGray(Color color) {
       int y = (int) (Math.round(intensity(color)));   // round to nearest int
       Color gray = new Color(y, y, y);
       return gray;
   }

   /**
    * Are the two given colors compatible? Two colors are compatible if the
    * the difference in their monochrome luminances is at least 128.0).
    *
    * @param a one color
    * @param b the other color
    * @return {@code true} if colors {@code a} and {@code b} are compatible;
    * {@code false} otherwise
    */
   public static boolean areCompatible(Color a, Color b) {
       return Math.abs(intensity(a) - intensity(b)) >= 128.0;
   }

   /**
    * Unit tests this {@code Image} data type.
    * Reads a image specified by the command-line argument,
    * and shows it in a window on the screen.
    *
    * @param args the command-line arguments
    */
   public static void main(String[] args) {
       Image image = new Image(args[0]);
       System.out.printf("%d-by-%d\n", image.width(), image.height());
       image.show();
   }

   /**
    * Returns a {@link JLabel} containing this image, for embedding in a {@link JPanel},
    * {@link JFrame} or other GUI widget.
    *
    * @return the {@code JLabel}
    */
   public JLabel getJLabel() {
       if (image == null) {
           return null;         // no image available
       }
       ImageIcon icon = new ImageIcon(image);
       return new JLabel(icon);
   }

   /**
    * Sets the origin to be the upper left pixel. This is the default.
    */
   public void setOriginUpperLeft() {
       isOriginUpperLeft = true;
   }

   /**
    * Sets the origin to be the lower left pixel.
    */
   public void setOriginLowerLeft() {
       isOriginUpperLeft = false;
   }

   /**
    * Displays the image in a window on the screen.
    */

   // getMenuShortcutKeyMask() deprecated in Java 10 but its replacement
   // getMenuShortcutKeyMaskEx() is not available in Java 8
   @SuppressWarnings("deprecation")
   public void show() {

       // create the GUI for viewing the image if needed
       if (frame == null) {
           frame = new JFrame();

           JMenuBar menuBar = new JMenuBar();
           JMenu menu = new JMenu("File");
           menuBar.add(menu);
           JMenuItem menuItem1 = new JMenuItem(" Save...   ");
           menuItem1.addActionListener(this);
           menuItem1.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S,
               Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
           menu.add(menuItem1);
           frame.setJMenuBar(menuBar);
           frame.setContentPane(getJLabel());
           // f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
           frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
           if (filename == null) {
               frame.setTitle(width + "-by-" + height);
           } else {
               frame.setTitle(filename);
           }
           frame.setResizable(false);
           frame.pack();
           frame.setVisible(true);
       }

       // draw
       frame.repaint();
   }

   /**
    * Returns the height of the image.
    *
    * @return the height of the image (in pixels)
    */
   public int height() {
       return height;
   }

   /**
    * Returns the width of the image.
    *
    * @return the width of the image (in pixels)
    */
   public int width() {
       return width;
   }

   private void validateRowIndex(int row) {
       if (row < 0 || row >= height()) {
           throw new IllegalArgumentException(
               "row index must be between 0 and " + (height() - 1) + ": " + row);
       }
   }

   private void validateColumnIndex(int col) {
       if (col < 0 || col >= width()) {
           throw new IllegalArgumentException(
               "column index must be between 0 and " + (width() - 1) + ": " + col);
       }
   }

   /**
    * Returns the color of pixel ({@code col}, {@code row}) as a {@link java.awt.Color}.
    *
    * @param col the column index
    * @param row the row index
    * @return the color of pixel ({@code col}, {@code row})
    * @throws IllegalArgumentException unless both {@code 0 <= col < width} and {@code 0 <= row < height}
    */
   public Color get(int col, int row) {
       validateColumnIndex(col);
       validateRowIndex(row);
       int rgb = getRGB(col, row);
       return new Color(rgb);
   }

   /**
    * Returns the color of pixel ({@code col}, {@code row}) as an {@code int}.
    * Using this method can be more efficient than {@link #get(int, int)} because
    * it does not create a {@code Color} object.
    *
    * @param col the column index
    * @param row the row index
    * @return the integer representation of the color of pixel ({@code col}, {@code row})
    * @throws IllegalArgumentException unless both {@code 0 <= col < width} and {@code 0 <= row < height}
    */
   public int getRGB(int col, int row) {
       validateColumnIndex(col);
       validateRowIndex(row);
       if (isOriginUpperLeft) {
           return image.getRGB(col, row);
       } else {
           return image.getRGB(col, height - row - 1);
       }
   }

   /**
    * Sets the color of pixel ({@code col}, {@code row}) to given color.
    *
    * @param col   the column index
    * @param row   the row index
    * @param color the color
    * @throws IllegalArgumentException unless both {@code 0 <= col < width} and {@code 0 <= row < height}
    * @throws IllegalArgumentException if {@code color} is {@code null}
    */
   public void set(int col, int row, Color color) {
       validateColumnIndex(col);
       validateRowIndex(row);
       if (color == null) {
           throw new IllegalArgumentException("color argument is null");
       }
       int rgb = color.getRGB();
       setRGB(col, row, rgb);
   }

   /**
    * Sets the color of pixel ({@code col}, {@code row}) to given color.
    *
    * @param col the column index
    * @param row the row index
    * @param rgb the integer representation of the color
    * @throws IllegalArgumentException unless both {@code 0 <= col < width} and {@code 0 <= row < height}
    */
   public void setRGB(int col, int row, int rgb) {
       validateColumnIndex(col);
       validateRowIndex(row);
       if (isOriginUpperLeft) {
           image.setRGB(col, row, rgb);
       } else {
           image.setRGB(col, height - row - 1, rgb);
       }
   }

   /**
    * Returns true if this image is equal to the argument image.
    *
    * @param other the other image
    * @return {@code true} if this image is the same dimension as {@code other}
    * and if all pixels have the same color; {@code false} otherwise
    */
   public boolean equals(Object other) {
       if (other == this) {
           return true;
       }
       if (other == null) {
           return false;
       }
       if (other.getClass() != this.getClass()) {
           return false;
       }
       Image that = (Image) other;
       if (this.width() != that.width()) {
           return false;
       }
       if (this.height() != that.height()) {
           return false;
       }
       for (int col = 0; col < width(); col++) {
           for (int row = 0; row < height(); row++) {
               if (this.getRGB(col, row) != that.getRGB(col, row)) {
                   return false;
               }
           }
       }
       return true;
   }

   /**
    * Returns a string representation of this image.
    * The result is a <code>width</code>-by-<code>height</code> matrix of pixels,
    * where the color of a pixel is represented using 6 hex digits to encode
    * the red, green, and blue components.
    *
    * @return a string representation of this image
    */
   public String toString() {
       StringBuilder sb = new StringBuilder();
       sb.append(width + "-by-" + height + " image (RGB values given in hex)\n");
       for (int row = 0; row < height; row++) {
           for (int col = 0; col < width; col++) {
               int rgb = 0;
               if (isOriginUpperLeft) {
                   rgb = image.getRGB(col, row);
               } else {
                   rgb = image.getRGB(col, height - row - 1);
               }
               sb.append(String.format("#%06X ", rgb & 0xFFFFFF));
           }
           sb.append("\n");
       }
       return sb.toString().trim();
   }

   /**
    * This operation is not supported because pictures are mutable.
    *
    * @return does not return a value
    * @throws UnsupportedOperationException if called
    */
   public int hashCode() {
       throw new UnsupportedOperationException(
           "hashCode() is not supported because pictures are mutable");
   }

   /**
    * Saves the image to a file in either PNG or JPEG format.
    * The filetype extension must be either .png or .jpg.
    *
    * @param name the name of the file
    * @throws IllegalArgumentException if {@code name} is {@code null}
    */
   public void save(String name) {
       if (name == null) {
           throw new IllegalArgumentException("argument to save() is null");
       }
       save(new File(name));
       filename = name;
   }

   /**
    * Saves the image to a file in a PNG or JPEG image format.
    *
    * @param file the file
    * @throws IllegalArgumentException if {@code file} is {@code null}
    */
   public void save(File file) {
       if (file == null) {
           throw new IllegalArgumentException("argument to save() is null");
       }
       filename = file.getName();
       if (frame != null) {
           frame.setTitle(filename);
       }
       String suffix = filename.substring(filename.lastIndexOf('.') + 1);
       if ("jpg".equalsIgnoreCase(suffix) || "png".equalsIgnoreCase(suffix)) {
           try {
               ImageIO.write(image, suffix, file);
           }
           catch (IOException e) {
               e.printStackTrace();
           }
       } else {
           System.out.println("Error: filename must end in .jpg or .png");
       }
   }

   /**
    * Opens a save dialog box when the user selects "Save As" from the menu.
    */
   @Override
   public void actionPerformed(ActionEvent e) {
       FileDialog chooser = new FileDialog(frame,
           "Use a .png or .jpg extension", FileDialog.SAVE);
       chooser.setVisible(true);
       if (chooser.getFile() != null) {
           save(chooser.getDirectory() + File.separator + chooser.getFile());
       }
   }

}

3 Rectangle

這段 Java 程式碼定義了一個不可變的 Rectangle 類別,用來表示位於二維平面正象限中的矩形。以下是詳細的中文解釋與設計分析:

🧱 類別名稱:Rectangle(矩形)

這個類別的設計目的是用來表示影像處理中常見的矩形區域,例如 ROI(Region of Interest)或裁剪範圍。它是 不可變的,也就是一旦建立後,座標值無法修改。

📐 成員變數說明

欄位名稱 中文說明
xTopLeft 左上角的 x 座標(必須 ≥ 0)
yTopLeft 左上角的 y 座標(必須 ≥ 0)
xBottomRight 右下角的 x 座標(必須 > xTopLeft)
yBottomRight 右下角的 y 座標(必須 > yTopLeft)

🔒 表示不變式(Representation Invariant)

這些條件保證矩形的幾何合法性:

  • 0 <= xTopLeft < xBottomRight
  • 0 <= yTopLeft < yBottomRight

這表示矩形必須有正面積,且位於第一象限。

🧰 建構子邏輯

java

public Rectangle(int _xTopLeft, int _yTopLeft, int _xBottomRight, int _yBottomRight)
  • 檢查輸入座標是否合法。
  • 若不合法(例如負值或右下角不在左上角右下方),則丟出 IllegalArgumentException
  • 若合法,則初始化四個座標欄位。

✅ 設計優點

  • 不可變性:使用 final 欄位,確保物件建立後不會被修改,提升安全性與可預測性。
  • 清楚的抽象函數與不變式:有助於教學與維護。
  • 錯誤防範:建構子中加入輸入驗證,避免建立非法矩形。

如果你打算將這個類別用於影像處理模組(例如裁剪、標記、或區域分析),我可以幫你擴充功能,例如:

  • 計算面積
  • 判斷是否重疊
  • 轉換為 Image 模組中的像素範圍
  • 加入 toString()equals() 方法以便測試與除錯

需要我幫你加上中文註解版本或撰寫教學文件嗎?我也可以幫你設計測試案例來驗證矩形合法性與邊界行為。

package ca.ubc.ece.cpen221.ip.core;

/**
* This datatype represents an <strong>immutable</strong> Rectangle
* in the positive quadrant of the 2D plane. These rectangles match
* standard image representations with the coordinate values for the
* top-left corner being smaller than the coordinate values for the
* bottom-right corner.
*
* After an instance is created, the coordinates of the top-left and bottom-right
* corners can be accessed using the constant fields
* <code>xTopLeft, yTopLeft, xBottomRight, yBottomRight</code>.
*/
public class Rectangle {

   public final int xTopLeft, yTopLeft;
   public final int xBottomRight, yBottomRight;

   /*
       Abstraction Function: Represents a rectangle using its top-left corner and bottom-right
       corner. xTopLeft is the x-coordinate of the top-left corner and yTopLeft is the y-coordinate
       of the top-left corner. xBottomRight and yBottom right are the x- and y- coordinates
       of the bottom-right corner.

       Representation Invariant:
           0 <= xTopLeft < xBottomRight
           0 <= yTopLeft < yBottomRight
   */

   /**
    * Create a new Rectangle.
    *
    * @param _xTopLeft: is the x coordinate of the top-left corner and should be >= 0
    * @param _yTopLeft: is the y coordinate of the top-left corner and should be >= 0
    * @param _xBottomRight: is the x coordinate of the bottom-right corner and should be > _xTopLeft
    * @param _yBottomRight: is the y coordinate of the bottom-right corner and should be > _yTopLeft
    */
   public Rectangle(int _xTopLeft, int _yTopLeft, int _xBottomRight, int _yBottomRight) {
       if (_xTopLeft < 0 || _yTopLeft < 0 ||
           _xBottomRight <= _xTopLeft || _yBottomRight <= _yTopLeft) {
           throw new IllegalArgumentException("Invalid rectangle specified.");
       }

       xTopLeft = _xTopLeft;
       yTopLeft = _yTopLeft;
       xBottomRight = _xBottomRight;
       yBottomRight = _yBottomRight;
   }

}

4 DFTOutput

這段 Java 程式碼定義了一個 DFTOutput 類別,用來表示空間離散傅立葉轉換(DFT)的輸出結果。以下是中文解析與設計重點:

📦 類別名稱:DFTOutput(離散傅立葉轉換輸出)

此類別封裝了 DFT 的兩個核心結果:

  • 振幅矩陣(Amplitude)
  • 相位矩陣(Phase)

這種表示方式是將複數形式的 DFT 結果拆解為兩個 DoubleMatrix,便於後續影像處理與視覺化。

🧩 類別成員與功能

成員/方法 中文說明
amplitude 儲存 DFT 的振幅部分(模長)
phase 儲存 DFT 的相位部分(角度)
DFTOutput(double[][] _amplitude, double[][] _phase) 建構子,從兩個二維陣列建立振幅與相位矩陣
equals(Object o) 比較兩個 DFTOutput 是否相等(振幅與相位皆相同)
hashCode() 使用振幅矩陣的雜湊碼作為整體雜湊值

🔒 表示不變式(Representation Invariant)

  • amplitude.width == phase.width
  • amplitude.height == phase.height

這保證兩個矩陣在維度上完全一致,才能對應每個像素的頻率成分。

🧠 設計重點

  • 使用 DoubleMatrix 類別來封裝數值矩陣,確保資料不可變且具備比較功能。
  • 建構子中進行維度一致性檢查,避免錯誤輸入。
  • 支援 .equals() 方法,便於測試與資料比對。
  • 使用 .hashCode() 方法支援雜湊結構(例如 HashMap)。

✅ 延伸建議

若你打算進一步處理 DFT 結果,例如:

  • 可視化頻譜(使用 log scale)
  • 反轉 DFT(IDFT)
  • 濾波器設計(低通、高通、帶通)

我可以幫你設計對應的模組與測試案例,並補上中英文註解與 README 文件。如果你希望這個類別能支援複數形式或與 Image 類別整合,也可以一起規劃。要不要我幫你補上 getAmplitude() / getPhase() 方法來讓外部模組存取這些矩陣?這樣會更模組化。

package ca.ubc.ece.cpen221.ip.mp;

import ca.ubc.ece.cpen221.ip.core.DoubleMatrix;

/**
* This datatype represents the output of a spatial Discrete Fourier Transform,
* and holds the amplitude and phase matrix obtained from the DFT.
*/
public class DFTOutput {
   private DoubleMatrix amplitude;
   private DoubleMatrix phase;

   /*
       Abstraction Function:
           Represents the output of the (spatial) DFT applied to an image.
           amplitude is a matrix that represents the amplitude portion of the DFT output.
           phase is a matrix that represents the phase portion of the DFT output.

       Note:
           A DFT can usually be represented by a matrix of complex numbers but
           we use the amplitude & phase representation using two matrices.

       Representation Invariant:
           amplitude.width == phase.width
           amplitude.height == phase.width
    */

   /**
    * Create a new DFTOutput instance.
    *
    * @param _amplitude is not null
    * @param _phase is not null, and is equal in dimensions to _amplitude
    */
   public DFTOutput(double[][] _amplitude, double[][] _phase) {
       amplitude = new DoubleMatrix(_amplitude);
       phase = new DoubleMatrix(_phase);
       if (amplitude.columns != phase.columns || amplitude.rows != phase.rows) {
           throw new IllegalArgumentException(
               "amplitude and phase matrices should have the same dimensions"
           );
       }
   }

   @Override
   public boolean equals(Object o) {
       if (!(o instanceof DFTOutput)) {
           return false;
       }
       DFTOutput other = (DFTOutput) o;
       return amplitude.equals(other.amplitude) && phase.equals(other.phase);
   }

   @Override
   public int hashCode() {
       return amplitude.hashCode();
   }
}

5 ImageProcessing

這段程式碼定義了 ImageProcessing 類別,目的是處理多張影像之間的比較與匹配。以下是中文解析與建議實作方向:

📦 類別名稱:ImageProcessing

此類別提供針對「多張影像」的操作方法,屬於影像處理模組中的高階邏輯部分。

🧠 方法說明與中文解釋

1. cosineSimilarity(Image img1, Image img2)

  • 功能目標:計算兩張影像之間的餘弦相似度(Cosine Similarity),用來衡量它們的相似程度。
  • 應用場景:影像比對、搜尋、分類。
  • 實作建議
    • 將影像轉換為向量(例如灰階像素展平為一維陣列)。
    • 使用公式:

similarity=A⃗⋅B⃗∥A⃗∥⋅∥B⃗∥\text{similarity} = \frac{\vec{A} \cdot \vec{B}}{|\vec{A}| \cdot |\vec{B}|}

  • 注意處理不同尺寸的影像(可能需先 resize 或 crop)。

2. bestMatch(Image img, List<Image> matchingCandidates)

  • 功能目標:從候選影像清單中找出與目標影像最相似的影像(或多張)。
  • 應用場景:影像搜尋、推薦系統。
  • 實作建議
    • 對每張候選影像使用 cosineSimilarity() 計算相似度。
    • 根據相似度排序,選出最佳匹配(可回傳 top-N 或 threshold 以上者)。
    • 回傳 List<Image> 以支援多個匹配結果。

✅ 延伸建議

如果你希望這個模組更完整,我可以幫你補上:

  • 影像向量化工具(例如 Image.toVector()
  • 灰階轉換與標準化方法
  • 測試案例與效能分析
  • 中英文註解與 README 文件

是否要我幫你撰寫 cosineSimilarity 的實作範例?我可以提供模組化、可測試的版本,並附上註解說明每一步。這樣會更適合教學與展示。

package ca.ubc.ece.cpen221.ip.mp;

import ca.ubc.ece.cpen221.ip.core.Image;

import java.util.List;

/**
* This class provides some simple operations involving
* more than one image.
*/
public class ImageProcessing {

   /* ===== TASK 3 ===== */

   public static double cosineSimilarity(Image img1, Image img2) {
       // TODO: Implement this method
       return -1;
   }

   public static List<Image> bestMatch(Image img, List<Image> matchingCandidates) {
       // TODO: Implement this method
       return null;
   }

}

6 ImageTransformer

這段程式碼定義了一個功能豐富的影像轉換類別 ImageTransformer,設計上非常適合進行模組化影像處理。以下是中文解析與建議補充:

🧱 類別名稱:ImageTransformer(影像轉換器)

此類別的目的是對影像進行各種視覺效果處理,並保證原始影像不被修改(不可變性設計)。

📌 成員變數說明

成員 中文說明
image 原始影像資料(不會被修改)
width / height 影像的寬度與高度,用於像素迴圈

✅ 已實作方法解析

grayscale()

  • 將影像轉換為灰階。
  • 使用 Image.toGray(Color) 將每個像素轉換為灰階色。

red()

  • 保留紅色通道,將綠色與藍色設為 0。
  • 使用位元操作提取 RGB 並重組像素。

🧠 待實作方法建議

TASK 1:基本視覺效果

方法 建議實作邏輯
mirror() 水平翻轉影像:將 (col, row) 映射為 (width - 1 - col, row)
negative() 對每個像素 (r, g, b) 計算 (255 - r, 255 - g, 255 - b)
posterize() 將色彩分段,例如每個通道只保留 4 個層級(類似漫畫風格)

TASK 2:進階處理

方法 建議實作邏輯
denoise() 使用中值濾波或高斯濾波,移除雜訊
weather() 模擬老舊或褪色效果,可加入隨機雜訊與色彩偏移
blockPaint(int blockSize) 將影像分成區塊,每塊用平均色填充(像素化效果)

TASK 4:綠幕合成

方法 建議實作邏輯
greenScreen(Color screenColour, Image backgroundImage) 將指定顏色區域替換為背景影像對應像素,可用色差閾值判斷

TASK 5:文字對齊

方法 建議實作邏輯
alignTextImage() 偵測文字區域並居中排列,可能需結合邊緣偵測與投影分析

🔧 建構子補充建議

目前 ImageTransformer(Image img) 尚未實作,建議如下:

java

public ImageTransformer(Image img) {
    if (img == null) {
        throw new IllegalArgumentException("Image cannot be null");
    }
    this.image = img;
    this.width = img.getWidth();
    this.height = img.getHeight();
}

📚 延伸支援

如果你希望這個模組能展示於教學或展示系統,我可以幫你:

  • 加上中英文註解
  • 撰寫 README 文件
  • 補上測試案例(JUnit)
  • 製作模擬腳本與範例輸出

是否要我幫你優化 mirror()negative() 的實作?我可以提供模組化、可測試的版本,並附註教學說明。這樣會更適合展示與教學用途。

package ca.ubc.ece.cpen221.ip.mp;

import ca.ubc.ece.cpen221.ip.core.Image;
import ca.ubc.ece.cpen221.ip.core.ImageProcessingException;
import ca.ubc.ece.cpen221.ip.core.Rectangle;

import java.awt.Color;

/**
* This datatype (or class) provides operations for transforming an image.
*
* <p>The operations supported are:
* <ul>
*     <li>The {@code ImageTransformer} constructor generates an instance of an image that
*     we would like to transform;</li>
*     <li></li>
* </ul>
* </p>
*/

public class ImageTransformer {

   private Image image;
   private int width;
   private int height;

   /**
    * Creates an ImageTransformer with an image. The provided image is
    * <strong>never</strong> changed by any of the operations.
    *
    * @param img is not null
    */
   public ImageTransformer(Image img) {
       // TODO: Implement this method
   }

   /**
    * Obtain the grayscale version of the image.
    *
    * @return the grayscale version of the instance.
    */
   public Image grayscale() {
       Image gsImage = new Image(width, height);
       for (int col = 0; col < width; col++) {
           for (int row = 0; row < height; row++) {
               Color color = image.get(col, row);
               Color gray = Image.toGray(color);
               gsImage.set(col, row, gray);
           }
       }
       return gsImage;
   }

   /**
    * Obtain a version of the image with only the red colours.
    *
    * @return a reds-only version of the instance.
    */
   public Image red() {
       Image redImage = new Image(width, height);
       for (int col = 0; col < width; col++) {
           for (int row = 0; row < height; row++) {
               int originalPixel = image.getRGB(col, row);
               int alpha = (originalPixel >> 24) & 0xFF;
               int red = (originalPixel >> 16) & 0xFF;
               int desiredColor = (alpha << 24) | (red << 16) | (0 << 8) | (0);
               redImage.setRGB(col, row, desiredColor);
           }
       }
       return redImage;
   }

   /* ===== TASK 1 ===== */

   /**
    * Returns the mirror image of an instance.
    *
    * @return the mirror image of the instance.
    */
   public Image mirror() {
       // TODO: Implement this method
       return null;
   }

   /**
    * <p>Returns the negative version of an instance.<br />
    * If the colour of a pixel is (r, g, b) then the colours of the same pixel
    * in the negative of the image are (255-r, 255-g, 255-b).</p>
    *
    * @return the negative of the instance.
    */
   public Image negative() {
       // TODO: Implement this method
       return null;
   }

   public Image posterize() {
       // TODO: Implement this method
       return null;
   }

   /* ===== TASK 2 ===== */

   public Image denoise() {
       // TODO: Implement this method
       return null;
   }

   /**
    * @return a weathered version of the image.
    */
   public Image weather() {
       // TODO: Implement this method
       return null;
   }

   public Image blockPaint(int blockSize) {
       // TODO: Implement this method
       return null;
   }

   /* ===== TASK 4 ===== */

   public Image greenScreen(Color screenColour, Image backgroundImage) {
       // TODO: Implement this method
       return null;
   }

   /* ===== TASK 5 ===== */

   public Image alignTextImage() {
       // TODO: Implement this method
       return null;
   }
}

7 Task1test

這是一組針對 ImageTransformer 類別的 JUnit 測試案例,屬於 Task 1 的驗證模組。以下是中文解析與建議補充:

🧪 測試類別:Task1Tests

此類別使用 JUnit 5 (org.junit.jupiter.api) 來測試三個影像處理功能是否正確:

測試方法 功能驗證 預期輸出檔案
test_Mirroring() 水平鏡像 15088-mirror.png
test_Negative() 色彩反轉 15088-negative.png
test_Posterize() 色彩分段 15088-poster.png

📌 測試流程解析

每個測試方法都遵循以下步驟:

  1. 載入原始影像

    java

    Image originalImg = new Image("resources/15088.jpg");
  2. 載入預期結果影像

    java

    Image expectedImg = new Image("resources/tests/15088-mirror.png");
  3. 建立轉換器並執行操作

    java

    ImageTransformer t = new ImageTransformer(originalImg);
    Image outputImage = t.mirror(); // 或 negative(), posterize()
  4. 比對結果是否一致

    java

    assertEquals(expectedImg, outputImage);

✅ 建議補充與強化

  • 測試覆蓋率:可加入邊界測試(例如空白影像、極小尺寸、單色影像)。
  • 錯誤處理測試:測試 null 輸入、無效檔案路徑、異常格式。
  • 視覺驗證工具:若 Image.equals() 使用像素比對,可加入像素差異視覺化工具以輔助除錯。
  • 效能測試:對大型影像進行處理時,測試執行時間與記憶體使用。

如果你希望我幫你補上 mirror()negative()posterize() 的實作,我可以提供模組化版本,附上中英文註解與測試案例。也可以幫你撰寫 README 文件來展示整個 Task1 的功能與教學流程。要不要我幫你補上 mirror() 的程式碼範例?我們可以一步步完成整個模組。

package ca.ubc.ece.cpen221.ip.mp;

import ca.ubc.ece.cpen221.ip.core.Image;

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class Task1Tests {

   @Test
   public void test_Mirroring() {
       Image originalImg = new Image("resources/15088.jpg");
       Image expectedImg = new Image("resources/tests/15088-mirror.png");
       ImageTransformer t = new ImageTransformer(originalImg);
       Image outputImage = t.mirror();
       assertEquals(expectedImg, outputImage);
   }

   @Test
   public void test_Negative() {
       Image originalImg = new Image("resources/15088.jpg");
       Image expectedImg = new Image("resources/tests/15088-negative.png");
       ImageTransformer t = new ImageTransformer(originalImg);
       Image outputImage = t.negative();
       assertEquals(expectedImg, outputImage);
   }

   @Test
   public void test_Posterize() {
       Image originalImg = new Image("resources/15088.jpg");
       Image expectedImg = new Image("resources/tests/15088-poster.png");
       ImageTransformer t = new ImageTransformer(originalImg);
       Image outputImage = t.posterize();
       assertEquals(expectedImg, outputImage);
   }

}

8 Task2test

package ca.ubc.ece.cpen221.ip.mp;

import ca.ubc.ece.cpen221.ip.core.Image;

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class Task2Tests {

   @Test
   public void test_Weathering() {
       Image originalImg = new Image("resources/95006.jpg");
       Image expectedImg = new Image("resources/tests/95006-weathered.png");
       ImageTransformer t = new ImageTransformer(originalImg);
       Image outputImage = t.weather();
       assertEquals(expectedImg, outputImage);
   }
}

這段程式碼是針對 ImageTransformer.weather() 方法的單元測試,屬於 Task 2 的驗證邏輯。以下是中文解析與建議補充:

🧪 測試方法:test_Weathering()

此測試目的是確認「風化效果」是否正確套用在影像上。

📌 測試流程:

  1. 載入原始影像

    java

    Image originalImg = new Image("resources/95006.jpg");
  2. 載入預期風化後影像

    java

    Image expectedImg = new Image("resources/tests/95006-weathered.png");
  3. 建立轉換器並執行 weather() 方法

    java

    ImageTransformer t = new ImageTransformer(originalImg);
    Image outputImage = t.weather();
  4. 比對結果是否一致

    java

    assertEquals(expectedImg, outputImage);

🌫️ weather() 方法預期行為

雖然尚未實作,但根據命名與測試目的,weather() 應模擬影像的老化或褪色效果,可能包括:

  • 色彩偏移(例如偏黃、偏灰)
  • 加入雜訊(模擬刮痕、灰塵)
  • 對比降低或模糊處理

✅ 建議補充

  • 多樣化測試:可加入不同解析度、色彩風格的影像進行測試。
  • 視覺差異容忍度:若 Image.equals() 是像素級比對,可能需加入容差機制(例如允許微小色差)。
  • 效能測試:對大型影像進行風化處理時,測試執行時間與記憶體使用。

如果你希望我幫你設計 weather() 的實作邏輯,我可以提供一個模組化版本,例如使用隨機雜訊疊加、色彩偏移矩陣或模糊濾波器。也可以幫你撰寫中英文註解與測試案例,讓整個 Task 2 更完整、可展示。要不要我幫你補上 weather() 的程式碼範例?我們可以一步步完成整個模組。

最後修改日期: 2025 年 10 月 13 日

作者

留言

撰寫回覆或留言

發佈留言必須填寫的電子郵件地址不會公開。