logo

额外区块类型 (EBT) - 全新的布局构建器体验❗

额外区块类型 (EBT) - 样式化、可定制的区块类型:幻灯片、标签页、卡片、手风琴等更多类型。内置背景、DOM Box、JavaScript 插件的设置。立即体验布局构建的未来。

演示 EBT 模块 下载 EBT 模块

❗额外段落类型 (EPT) - 全新的 Paragraphs 体验

额外段落类型 (EPT) - 类似的基于 Paragraph 的模块集合。

演示 EPT 模块 滚动

滚动

PHP课程 - 第4课 - 图像处理与GD2库的使用

07/10/2025, by Ivan

在前几课中,我们已经学习了如何编写数据库查询,因此这节课我们将少关注“怎么写查询”,而更多地进行实际练习。同时,我们会把查询编写与其他 PHP 功能结合起来,从图像处理开始。在之前的课程中,我们已经实现了文件上传,并且数据库中已有一个 Files 表来存储上传的文件。这次,我们将把图片也上传到同一个表中。但首先,需要在内容创建表单中添加一个上传图片的字段。

$content .= '<label for="bodytext">消息:</label><br />';
$content .= '<textarea name="bodytext" id="bodytext"></textarea><br />';
$content .= '<label for="bodytext">附加文件:</label><br /><input type="file" name="filename"><br />';
$content .= '<label for="bodytext">附加图片:</label><br /><input type="file" name="image">';

接下来我们要编写文件处理逻辑,但首先需要在 Messages 表中添加一列 image_fid,用于存储图片的 fid。图片的其他信息依然会存储在 Files 表中,就像普通文件一样。image_fid 类型为 INT(11),默认值为 NULL。现在我们来修改 write() 方法,添加上传处理逻辑:

if($_FILES["image"]["size"] > 1024*3*1024){
   echo ("文件大小超过3MB");
   exit;
}
if(is_uploaded_file($_FILES["image"]["tmp_name"])){ // 检查文件是否上传成功
  move_uploaded_file($_FILES["image"]["tmp_name"], "./files/".$_FILES["filename"]["name"]);
} else {
  echo("文件上传出错");
}

$sql = 'INSERT INTO Files (filename, filepath, filemime, filesize, timestamp)
VALUES ("'. $_FILES['image']['name'] . '",
"files/' . $_FILES['image']['name'] . '",
"'. $_FILES['image']['type'] .'",
'. $_FILES['image']['size'] .',
'. time() . ')';

mysql_query($sql);

这个处理逻辑与普通文件上传几乎没有区别。在后续课程中,我们将学习如何检查文件类型,以防止有人伪装成图片上传 PDF 等非图像文件。

现在我们可以上传两个文件,其中一个是图片。打印 $_POST$_FILES 后的结构如下:

Array (
  [title] => test
  [bodytext] => asdfasf
)
Array (
  [filename] => Array (
    [name] => ip.txt
    [type] => text/plain
    [tmp_name] => Y:\tmp\php2C5.tmp
    [error] => 0
    [size] => 13
  )
  [image] => Array (
    [name] => Coat_of_arms_of_Vladimiri_Oblast.png
    [type] => image/png
    [tmp_name] => Y:\tmp\php2C6.tmp
    [error] => 0
    [size] => 393743
  )
)

接着我们要修改插入消息的 SQL:

$sql = 'INSERT INTO Messages (title, bodytext, created, fid, image_fid)
VALUES ("'. $p["title"] . '", "' . $p["bodytext"] . '", ' . time() . ', LAST_INSERT_ID()-1, LAST_INSERT_ID())';

这里使用 LAST_INSERT_ID() 来获取刚插入的文件 ID。由于普通文件先于图片插入,因此我们减去 1 来获取文件的 ID。

图片插入数据库后,我们可以显示这些图片。为此,我们将编写一个新的类 simpleImage 来处理图片。新建文件 class/simpleImage.php 并写入以下内容:

<?php

class simpleCMS {

}

?>

我们已经创建了一个用于图像处理的类,接下来将为它添加方法。但首先,来了解一下 PHP 中的类属性。

PHP 5 中的类属性

在 PHP4 中,类属性通过 var 定义。而在 PHP5 中,引入了更多控制属性可见性的关键字:publicprotectedprivate

Public

public 表示属性是公开的,可以在对象外部访问。例如:

<?php
$obj = new simpleCMS();
$obj->db = 'newDB';
?>

由于属性定义为 public,我们可以在对象外直接修改它的值。

Protected

protected 属性只能在类的内部或其子类中访问。如果我们将 $db 定义为 protected 并尝试外部访问,将会报错:

simpleCMS.php
...
protected $db = 'testDB';
...

index.php
...
$obj = new simpleCMS();
$obj->db = 'newDB';
...

现在我们添加一个受保护的变量 $path,用于存储图像路径。我们将通过类构造函数传入这个路径。

类的构造函数

构造函数用于初始化对象的默认状态。我们会用它来设置图像路径。构造函数名以两个下划线开头:

class simpleCMS {
  protected $path;

  public function __construct($image) {
    $this->path = $image;
  }
}

现在我们通过构造函数将文件路径赋值给受保护属性 $path。接着,我们修改 public_display 方法以输出图片信息:

if(!empty($row['image_fid'])){
  $sql = "SELECT * FROM Files WHERE fid=".$row['image_fid'];
  $image_query = mysql_query($sql) or die(mysql_error());
  $image = mysql_fetch_array($image_query);
  $content .= '<div class="image">';
    $image = new simpleImage($image['filepath']);
    $content .= $image->scale(100,100);
  $content .= '</div>';
  unset($image);
}

这样我们就可以从数据库中获取图片路径。现在来编写缩放(scale)与裁剪(scale_and_crop)方法。

public function scale($width,$height){

}
public function scale_and_crop($width,$height){

}

缩放图像(scale)

public function scale($width, $height){
  $path = $this->path;
  $filepath = explode('/', $this->path);
  $filename = end($filepath);
  $filename = substr($filename, 0, -4);
  $filename = str_replace(' ', '_', $filename);
  $extenstion = substr($this->path, -4);

  switch ($extenstion){
    case '.png': $srcImage = ImageCreateFromPNG($path); break;
    case '.jpg': $srcImage = ImageCreateFromJPEG($path); break;
    case '.gif': $srcImage = ImageCreateFromGIF($path); break;
    default: $srcImage = ImageCreateFromGIF($path);
  }

  $srcWidth = ImageSX($srcImage);
  $srcHeight = ImageSY($srcImage);

  $ratioWidth = $srcWidth/$width;
  $ratioHeight = $srcHeight/$height;

  if($ratioWidth < $ratioHeight){
    $destWidth = $srcWidth/$ratioHeight;
    $destHeight = $height;
  } else {
    $destWidth = $width;
    $destHeight = $srcHeight/$ratioWidth;
  }

  $destImage = imagecreate($destWidth, $destHeight);
  ImageCopyResized($destImage, $srcImage, 0, 0, 0, 0, $destWidth, $destHeight, $srcWidth, $srcHeight);
  imagejpeg($destImage, 'files/presets/'.$width.'_'.$height.'_'.$filename.'.jpg', 100);
  $newImagePath = 'files/presets/'.$width.'_'.$height.'_'.$filename.'.jpg';

  ImageDestroy($srcImage);
  ImageDestroy($destImage);
  return '<img src="'.$newImagePath.'" width="'.$destWidth.'" height="'.$destHeight.'" />';
}

生成的缩略图将保存在 files/presets 文件夹中,请确保该文件夹存在并设置为 777 权限。

缩放并裁剪图像(scale_and_crop)

public function scale_and_crop($width, $height){
  $path = $this->path;
  $filepath = explode('/', $this->path);
  $filename = end($filepath);
  $filename = substr($filename, 0, -4);
  $filename = str_replace(' ', '_', $filename);
  $extenstion = substr($this->path, -4);

  switch ($extenstion){
    case '.png': $srcImage = ImageCreateFromPNG($path); break;
    case '.jpg': $srcImage = ImageCreateFromJPEG($path); break;
    case '.gif': $srcImage = ImageCreateFromGIF($path); break;
    default: $srcImage = ImageCreateFromGIF($path);
  }

  $srcWidth = ImageSX($srcImage);
  $srcHeight = ImageSY($srcImage);

  $ratioSource = $srcWidth / $srcHeight;
  $ratioNew = $width / $height;

  if($ratioSource < $ratioNew){
    $newWidth = $srcWidth;
    $newHeight = $srcHeight / $ratioNew * $ratioSource;
  } else {
    $newWidth = $srcWidth / $ratioSource * $ratioNew;
    $newHeight = $srcHeight;
  }

  $destImage = imagecreate($width, $height);
  imagecopyresampled($destImage, $srcImage, 0, 0, 0, 0, $width, $height, $newWidth, $newHeight);
  imagejpeg($destImage, 'files/presets/'.$width.'_'.$height.'_'.$filename.'.jpg', 100);
  $newImagePath = 'files/presets/'.$width.'_'.$height.'_'.$filename.'.jpg';

  ImageDestroy($srcImage);
  ImageDestroy($destImage);
  return '<img src="'.$newImagePath.'" />';
}

更新 display_public() 方法:

$content .= '<div class="image">';
  $image = new simpleImage($image['filepath']);
  $content .= $image->scale_and_crop(100,100);
$content .= '</div>';

这样,图像将被缩放并裁剪为方形比例。

需要注意的是,这些图片都是“即时生成”的。由于生成图像需要一定时间,通常会将缩略图路径保存到数据库中,以避免重复生成。在本课程中,我们不会深入讲解这一优化,因为接下来的 CMS 开发课程将基于 Zend Framework,而当前的 PHP 课程旨在帮助你掌握 PHP 基础与图像处理原理。