图片处理(Picture Lab)活动5:修改图片

陈 欣发布

尽管数字图片有以百万计的像素,现代电脑的运行速度已经足够快,能够在一瞬间将它们全部处理完。你将在class Picture中编写处理数字图片的方法。class Picture继承了class SimplePicture,而后者又实现了interface DigitalPicture。它们之间的关系由下面的统一模型语言(Unified Modeling Language, UML)的图示所表示。

UML图示显示class间的关系。每个class表示为方框,而最顶部是class名。中间部分是class中的field,而最底下显示class的各个method。三角箭头表示继承关系,由child class指向parent class。直线表示组成关系(“has-a” relationship),数字则表示有多少个object。比如上图中,SimplePicture可以包含一至多个Pixel,而一个Pixel里只包含一个Color。UML独立于具体的编程语言,因而其method的写法和Java签名有一定的区别。

问题

  1. 打开Picture.java并查找getPixels2D method。
  2. 打开SimplePicture.java并查找getPixels2D method。
  3. 以下代码能通过编译么?
    DigitalPicture p = new DigitalPicture();
  4. 假定SimplePicture有默认构造函数,以下代码能通过编译么?
    DigitalPicture p = new SimplePicture();
  5. 假定Picture有默认构造函数,以下代码能通过编译么?
    DigitalPicture p = new Picture();
  6. 假定Picture有默认构造函数,以下代码能通过编译么?
    SimplePicture p = new Picture();
  7. 假定SimplePicture有默认构造函数,以下代码能通过编译么?
    Picture p = new SimplePicture();

DigitalPicture是一个interface。一般而言,interface只有public abstract的method。abstract method通常没有具体实现,你可以观察到DigitalPicture中的所有method都没有任何Java语句来描述它们的执行逻辑。没有实现的method有什么用处呢?

通过interface,我们能把什么如何区分开来。interface确定某个类型的object能做什么,但不管它如何做到这一点。你并不能直接把interface实例化,不过某个class可以实现interface,就像SimplePicture所做的那样。非abstract的class会为interface中声明的所有method提供具体实现。这可以是直接编写,也可通过继承的方式。你可以将变量声明为interface的类型,再给它赋值成任意一个实现了该interface的class的object。例如,Java有名为List的interface,声明List应该有addremoveget等method。但是一个List的object在AP计算机中则会是ArrayList

List<String> nameList = new ArrayList<String>();

为什么你不直接将nameList声明为ArrayList呢?事实上,Java中还有List的其他不同实现。将nameList定义为List而非ArrayList,能够方便你在以List为参数的函数中使用它,以及你的代码在未来转换到List的另一个实现上。只要你的代码仅利用到interface本身定义的一系列功能,interface就能给开发带来灵活性,减小在未来可能需要的代码修改。

因为DigitalPicture定义了一个getPixels2D method以返回Pixel object的二维数组,SimplePicture实现了这个interface而Picture又继承了SimplePicture,你可以直接在Picture object上调用getPixels2D 。之后,你就能通过对二维数组进行循环操作每个Pixel的方式来修改图片了。你既可以设置每个Pixel的红、绿和蓝色值,也可以一次读取或修改整个Color。你可以使用Color的构造函数,通过提供红、绿和蓝色值的方式来创建新Color object。

Color myColor = new Color(255,30,120);

你觉得把images目录下的海滩图片的所有蓝色值都清零之后会看到什么?觉得还能看到海滩么?运行class Picturemain method。其会从名为beach.jpg的图像文件读取数据并创建对应的Picture object,在探索窗口中显示(内存中的)图片,调用清零蓝色值的method,再另开一个探索窗口显示处理结果。

class Picturemain method由以下代码所示:

public static void main(String[] args)
{
  Picture beach = new Picture("beach.jpg");
  beach.explore();
  beach.zeroBlue();
  beach.explore();
}

练习

  1. 打开PictureTester.java并运行其main method。你应该得到和运行class Picturemain method一致的结果。class PictureTester包含测试class Picturestatic method。
  2. PictureTestermain method中的其他语句取消注释,测试Picture.java的其他method。对于不想运行的测试可以再度注释掉。你还可以在PictureTester添加针对你自己在Picture里新编写的method的测试。

class PicturezeroBlue method从当前图片中获取对应的Pixel object的二维数组。接下来,它使用嵌套for-each循环遍历图片的每个像素。在内层循环中,当前像素(定义的名为pixelObjPixel object)的蓝色值被设为零。注意for-each循环不能改变数组本身的值,但对于对象数组,可以改变每个对象的内部状态。

class PicturezeroBlue method由以下代码所示:

public void zeroBlue()
{
  Pixel[][] pixels = this.getPixels2D();
  for (Pixel[] rowArray : pixels)
  {
    for (Pixel pixelObj : rowArray)
    {
      pixelObj.setBlue(0);
    }
  }
}

练习

  1. zeroBlue方法为起点,编写只保留蓝色值,即清零红色和绿色值的keepOnlyBlue method。在class PictureTester中创建一个测试新method的static method,并确保在PictureTestermain method中调用它。
  2. 编写negate method来将图片中的所有像素取反色,即把红色值设为255减去原红色值,绿色值设为255减去原绿色值,蓝色值设为255减去原蓝色值。在class PictureTester中创建一个测试新method的static method,并确保在PictureTestermain method中调用它。
  3. 编写grayscale method来将图片转换为灰度,即将红、绿和蓝色值均设为它们三者的平均值(求和之后除3)。在class PictureTester中创建一个测试新method的static method,并确保在PictureTestermain method中调用它。
  4. 挑战——探索images目录下的water.jpg图片。编写fixUnderwater method来修改像素颜色,使得鱼能够更清晰可见。在class PictureTester中创建一个测试新method的static method,并确保在PictureTestermain method中调用它。

陈 欣

AADPS创始人

0 条评论

发表回复