Nodejs Sharp 简明教程

Nodejs Sharp 简明教程

简介

Node.js 有一个可用于处理图像的库生态系统,例如sharp、jimp 和gm 模块。本文将重点介绍sharp模块。 Sharp 是一个流行的 Node.js 图像处理库,支持各种图像文件格式,例如 JPEG、PNG、GIF、WebP、AVIF、SVG 和 TIFF。

在本教程中,您将使用Sharp 读取图像并提取其元数据、调整大小、更改图像格式和压缩图像。然后,您将裁剪、灰度化、旋转和模糊图像。最后,您将合成图像,并在图像上添加文本。在本教程结束时,您将很好地了解如何在 Node.js 中处理图像。

准备

开始教程之前您需要:

  • 在您的系统上安装 Node.js 和 npm。
  • 编写和运行 Node.js 程序的基本知识。
  • 对 JavaScript 异步编程的基本理解。 按照了解 JavaScript 中的事件循环、回调、承诺和 Async/Await 来查看异步编程。

第1步 - 设置项目目录并下载图像

在开始编写代码之前,您需要创建包含本文中将使用的代码和图像的目录。

打开终端并使用 mkdir 命令为项目创建目录:

mkdir process_images

使用 cd 命令进入新创建的目录:

cd process_images

使用 npm init 命令创建 package.json 文件以跟踪项目依赖项:

npm init -y

-y 选项告诉 npm 创建默认的 package.json 文件。

接下来,安装sharp作为依赖:

npm install sharp

您将在本教程中使用以下三个图像:


image.png

image.png

image.png

在本页面右键保存图片即可。

设置项目目录和依赖项后,您现在可以开始处理图像了。

第2步 - 读取图像并输出元数据

在本节中,您将编写代码来读取图像并提取其元数据。图像元数据是嵌入到图像中的文本,其中包括有关图像的信息,例如其类型、宽度和高度。

要提取元数据,您将首先导入sharp 模块,创建一个sharp实例,并将图像路径作为参数传递。之后,您将metadata()方法链接到实例以提取元数据并将其登录到控制台。

为此请在文本编辑器中创建并打开readImage.js文件。接下来,需要在文件顶部引用sharp:

const sharp = require("sharp");

sharp 是一个基于promise的图像处理模块。
当您创建一个 sharp 的实例时,它会返回一个promise。
您可以使用 then 方法或使用具有更清晰语法的 async/await 来解析承诺。

要使用 async/await 语法,您需要通过将 async 关键字放在函数的开头来创建一个异步函数。这将允许您在函数内使用 await 关键字来解析读取图像时返回的承诺。

在您的 readImage.js 文件中,定义一个异步函数 getMetadata() 来读取图像、提取其元数据并将其登录到控制台:

const sharp = require("sharp");

async function getMetadata() {
  const metadata = await sharp("sammy.png").metadata();
  console.log(metadata);
}

在function之前加上 async 关键字,getMetadata()就变成一个异步函数。这使您可以在函数中使用 await 语法。getMetadata() 函数将读取图像并返回一个带有元数据的对象。

在函数体中,您通过调用sharp() 来读取图像,该函数将图像路径作为参数,这里是sammy.png。

除了获取图像路径外,sharp() 还可以读取存储在 Buffer、Uint8Array 或 Uint8ClampedArray 中的图像数据,前提是图像是 JPEG、PNG、GIF、WebP、AVIF、SVG 或 TIFF。

现在,当您使用sharp() 读取图像时,它会创建一个sharp实例。然后将sharp模块的metadata()方法链接到实例。该方法返回一个包含图像元数据的对象,您将其存储在元数据变量中并使用console.log()记录其内容。

您的程序现在可以读取图像并返回其元数据。但是,如果程序在执行过程中抛出错误,就会崩溃。为了解决这个问题,您需要在错误发生时捕获它们。

为此,需要将getMetadata()函数中的代码包装在try...catch块中:

const sharp = require("sharp");

async function getMetadata() {
  try {
    const metadata = await sharp("sammy.png").metadata();
    console.log(metadata);
  } catch (error) {
    console.log(`An error occurred during processing: ${error}`);
  }
}

try块中,您读取图像、提取并打印其元数据。在此过程中发生错误时,执行会跳到catch部分并记录错误以防止程序崩溃。

保存文件,并执行下面的node命令:

node readImage.js

您应该会看到与此类似的输出:

{
  format: 'png',
  width: 750,
  height: 483,
  space: 'srgb',
  channels: 3,
  depth: 'uchar',
  density: 72,
  isProgressive: false,
  hasProfile: false,
  hasAlpha: false
}

现在您已经读取了图像并提取了其元数据,接下来将学习调整图像大小、更改其格式并对其进行压缩。

第3步 — 调整大小、更改图像格式和压缩图像

调整大小是更改图像尺寸,而不从中剪切任何内容的过程,这会影响图像文件大小。在本节中,您将调整图像大小、更改其图像类型并压缩图像。图像压缩是在不降低质量的情况下减小图像文件大小的过程。

首先,您将从sharp实例中resize()方法来调整图像大小,并将其保存在项目目录中。其次,您将使用format()方法调整大小的图像以将其格式从png更改为jpeg。 此外,还需要向format()方法传递一个参数以压缩图像并将其保存到目录中。

在文本编辑器中创建并打开resizeImage.js文件,添加以下代码以将图像大小调整为宽150px,高97px:

const sharp = require("sharp");

async function resizeImage() {
  try {
    await sharp("sammy.png")
      .resize({
        width: 150,
        height: 97
      })
      .toFile("sammy-resized.png");
  } catch (error) {
    console.log(error);
  }
}

resizeImage();

resizeImage()函数调用sharp实例的resize()方法。该方法将一个对象作为参数。在这对象中可以使用 width 和 height 属性设置所需的图像尺寸。将宽度设置为150,将高度设置为97 ,将使图像宽150像素,高97像素。

调整图像大小后,调用sharp实例的toFile()方法,该方法将图像路径作为参数。将sammy-resized.png作为参数传递将使用该名称将图像文件保存在程序的工作目录中。

现在,保存并退出文件。 在终端中运行你的程序:

node resizeImage.js

您不会得到任何输出,但您应该会在项目目录中看到一个名为 sammy-resized.png 的新图像文件。

image.png

现在您已学会调整图像大小,接下来您将图像格式从png转换为jpeg,压缩图像,并将其保存在工作目录中。 为此,您将使用toFormat()方法,该方法将在resize()方法之后调用。

const sharp = require("sharp");

async function resizeImage() {
  try {
    await sharp("sammy.png")
      .resize({
        width: 150,
        height: 97
      })
      .toFormat("jpeg", { mozjpeg: true })
      .toFile("sammy-resized-compressed.jpeg");
  } catch (error) {
    console.log(error);
  }
}

resizeImage();

resizeImage()函数中,您使用sharp模块的toFormat()方法来更改图像格式并对其进行压缩。 toFormat()方法的第一个参数是一个字符串,其中包含要将图像转换为的图像格式。第二个参数是一个可选对象,包含增强和压缩图像的输出选项。

要压缩图像,您需要传递一个包含布尔值的mozjpeg属性。当您将其设置为 true 时,sharp 使用 mozjpeg 默认值来压缩图像而不牺牲质量。对象还可以有更多的选择;有关更多详细信息,请参阅官方文档

注意:关于 toFormat() 方法的第二个参数,每种图像格式都采用具有不同属性的对象。 例如,仅在 JPEG 图像上接受 mozjpeg 属性。但是,其他图像格式具有等效选项,例如qualitycompressionlossless。 请务必参阅官方文档以了解您正在压缩的图像格式可接受的选项类型。

接下来,您向toFile()方法传递一个不同的文件名,以将压缩图像保存为sammy-resized-compressed.jpeg

现在,保存并退出文件,然后使用以下命令运行您的代码:

node resizeImage.js

您不会收到任何输出,但图像文件 sammy-resized-compressed.jpeg 会保存在您的项目目录中。

在本地机器上打开图像,您将看到以下图像:

image.png

在您的终端中,运行 du 命令可检查 sammy.png 的文件大小是否压缩成功:

du -h sammy.png

-h 选项生成人类可读的输出,显示文件大小(以千字节、兆字节等为单位)。

运行该命令后,您应该会看到类似于以下内容的输出:

120K    sammy.png

输出显示原始图像为 120KB。

接下来,检查 sammy-resized.png 的文件大小:

du -h sammy-resized.png

运行该命令后,您将看到以下输出:

8.0K sammy-resized.png

sammy-resized.png 从 120KB 减少到 8KB。 这表明调整大小操作会影响文件大小。

现在,检查 sammy-resized-compressed.jpeg 的文件大小:

du -h sammy-resized-compressed.jpeg

运行该命令后,您将看到以下输出:

4.0K sammy-resized-compressed.jpeg

sammy-resized-compressed.jpeg 现在从 8KB 减少到 4KB,节省了 4KB,表明压缩有效。

现在您已学会调整图像大小、更改其格式并对其进行压缩,您将对图像进行裁剪和灰度处理。

第4步 - 裁剪图像并将其转换为灰度

在这一步中,您将裁剪图像,并将其转换为灰度。裁剪是从图像中去除不需要的区域的过程。您将使用extend()方法裁剪 sammy.png 图像。之后,您会将grayscale()方法链接到裁剪后的图像实例并将其转换为灰度。

在文本编辑器中创建并打开cropImage.js,添加以下代码以裁剪图像:

const sharp = require("sharp");

async function cropImage() {
  try {
    await sharp("sammy.png")
      .extract({ width: 500, height: 330, left: 120, top: 70  })
      .toFile("sammy-cropped.png");
  } catch (error) {
    console.log(error);
  }
}

cropImage();

cropImage() 函数是一个异步函数,它读取图像并返回裁剪后的图像。在 try 块中,sharp将读取图像,然后调用sharp实例的extract() 方法采用具有以下属性的对象:

  • width:要裁剪的区域的宽度。
  • height:要裁剪的区域的高度。
  • top:要裁剪的区域的垂直位置。
  • left:要裁剪的区域的水平位置。

当您将宽度设置为500,并将高度设置为330时,想象一下Sharp在您要裁剪的图像顶部创建一个透明框。适合框的图像的任何部分将保留,其余部分将被剪切:

image.png

top 和 left 属性控制框的位置。当您将 left 设置为 120 时,框位于距图像左边缘 120px 的位置,将 top 设置为 70 将框定位在距图像顶部边缘 70px 的位置。

适合框的图像区域将被提取出来并作为单独的图像保存到 sammy-cropped.png 中。

保存并退出文件。在终端中运行程序:

node cropImage.js

图像 sammy-cropped.png 将保存在您的项目目录中。在本地机器上打开图像。您应该看到图像被裁剪:

image.png

现在您裁剪了图像,您将把图像转换为灰度。为此,您将调用grayscale方法。

const sharp = require("sharp");

async function cropImage() {
  try {
    await sharp("sammy.png")
      .extract({ width: 500, height: 330, left: 120, top: 70 })
      .grayscale()
      .toFile("sammy-cropped-grayscale.png");
  } catch (error) {
    console.log(error);
  }
}

cropImage();

cropImage() 函数通过调用sharp实例的grayscale()方法,将裁剪后的图像转换为灰度。然后将图像保存在项目目录中作为sammy-cropped-grayscale.png

在终端中运行您的代码:

node cropImage.js

在本地机器上打开 sammy-cropped-grayscale.png。 您现在应该可以看到灰度图像:

image.png

现在您已学会裁剪和提取了图像,您将使用旋转和模糊处理它。

第5步 - 旋转和模糊图像

在这一步中,您将以 33度角旋转sammy.png图片。您还将在旋转后的图像上应用高斯模糊。高斯模糊是一种使用高斯函数模糊图像的技术,它降低了图像的噪声水平和细节。

在文本编辑器中创建一个 rotateImage.js 文件,编写以下代码块以创建一个将 sammy.png 旋转到 33 度角的函数:

const sharp = require("sharp");

async function rotateImage() {
  try {
    await sharp("sammy.png")
      .rotate(33, { background: { r: 0, g: 0, b: 0, alpha: 0 } })
      .toFile("sammy-rotated.png");
  } catch (error) {
    console.log(error);
  }
}

rotateImage();

rotateImage() 函数是一个异步函数,它读取图像并返回旋转到33度角的图像。在该函数中,sharp实例的 rotate() 方法接受两个参数。第一个参数是旋转角度33度。默认情况下,sharp 将旋转图像的背景设为黑色。要移除黑色背景,您需要传递一个对象作为第二个参数以使背景透明。

该对象具有一个背景属性,该属性包含一个定义 RGBA 颜色模型的对象。 RGBA 代表红色、绿色、蓝色和 alpha。

r:控制红色的强度。它接受 0-255 之间的整数值。0 表示不使用红色,255 表示红色最高。
g:控制绿色的强度。它接受 0-255 之间的整数值。0 表示不使用绿色,255 表示绿色最高。
b:控制蓝色的强度。它接受 0-255 之间的整数值。0 表示不使用蓝色, 255 表示蓝色最高。
alpha:控制由 r、g 和 b 属性定义的颜色的不透明度。 0 或 0.0 使颜色透明,1 或 1.1 使颜色不透明。
要使 alpha 属性起作用,您必须确保设置 r、g、 b 的值。将 r、g 、b 值设置为 0 会创建黑色。要创建透明背景,必须先定义颜色,然后可以将 alpha 设置为 0 使其透明。

现在,保存文件,在终端中运行你的脚本:

node rotateImage.js

检查项目目录中是否存在 sammy-rotated.png。 在本地机器上打开它。

您应该看到旋转到 33 度角的图像:

image.png

接下来,您将对旋转后的图像进行模糊处理。 您将通过调用blur()方法来实现这一点。

const sharp = require("sharp");

async function rotateImage() {
  try {
    await sharp("sammy.png")
      .rotate(33, { background: { r: 0, g: 0, b: 0, alpha: 0 } })
      .blur(4)
      .toFile("sammy-rotated-blurred.png");
  } catch (error) {
    console.log(error);
  }
}

rotateImage();

rotateImage() 函数读取图像,旋转,并对图像应用高斯模糊。它使用sharp模块的blur()方法对图像应用高斯模糊。该方法接受一个 0.3-1000之间的参数。把4传递给它,将应用sigma值为4的高斯模糊。图像模糊后保存模糊图像。

保存文件,然后在终端中运行脚本:

node rotateImage.js

运行脚本后,在本地机器上打开 sammy-rotated-blurred.png 文件。您应该看到旋转后的图像变得模糊:

image.png

现在您已经学会旋转并模糊了图像,您将在另一个图像上合成一个图像。

第6步 - 使用composite()合成图像

图像合成是将两张或多张单独的图片组合在一起以创建单个图像的过程。这样做是为了创建从不同照片中借用最佳元素的效果。另一个常见的用例是为给图像添加水印。

在本节中,您将在 underwater.png 上合成 sammy-transparent.png。 这会产生一种鲨鱼在海洋深处游泳的错觉。要合成图像,您需要将调用composite()方法。

在文本编辑器中创建并打开文件 compositeImage.js,编写以下代码:

const sharp = require("sharp");

async function compositeImages() {
  try {
    await sharp("underwater.png")
      .composite([
        {
          input: "sammy-transparent.png",
          top: 50,
          left: 50,
        },
      ])
      .toFile("sammy-underwater.png");
  } catch (error) {
    console.log(error);
  }
}

compositeImages()

compositeImages() 函数首先读取underwater.png。接下来,调用composite()方法,该方法接受一个数组作为参数。该数组包含一个读取sammy-transparent.png图像的对象。该对象具有以下属性:

  • input:采用要在处理过的图像上合成的图像的路径。它还接受 Buffer、Uint8Array 或 Uint8ClampedArray 作为输入。
  • top:控制要合成的图像的垂直位置。将 top 设置为 50 会使 sammy-transparent.png 图像从underwater.png图像的顶部边缘偏移50px。
  • left:控制要在另一个图像上合成的图像的水平位置。将 left 设置为 50 会使 sammy-transparent.pngunderwater.png 图像的左边缘偏移50px。

composite() 方法需要与处理后的图像大小相似或更小的图像。

为了看到composite()方法正在做什么,可以把它想象成创建一堆图像。 sammy-transparent.png 图像放置在 underwater.png 图像之上:

image.png

top和left的值相对于underwater.png图像定位sammy-transparent.png图像。

保存文件,运行以下代码:

node compositeImages.js

在本地机器上打开sammy-underwater.png。您现在应该看到sammy-transparent.png合成在underwater.png图像上:

image.png

您现在已经使用 composite() 方法合成了图像。 在下一步中,您将使用 composite() 方法向图像添加文本。

第7步 - 在图像上添加文本

在这一步中,您将在图像上书写文本。在撰写本文时,sharp没有向图像添加文本的原生方式。要添加文本,首先,您将编写代码以使用可缩放矢量图形 (SVG) 绘制文本。创建 SVG 图像后,您将编写代码以使用复合方法将图像与 sammy.png 图像合成。

SVG 是一种基于 XML 的标记语言,用于为 Web 创建矢量图形。您可以绘制文本或圆形、三角形等形状,也可以绘制复杂的形状,例如插图、徽标等。使用生成 SVG 代码的 inkscape 等图形工具创建复杂的形状。 SVG形状可以渲染和缩放到任何大小而不会降低质量。

在文本编辑器中创建并打开 addTextOnImage.js 文件,编写以下代码:

const sharp = require("sharp");

async function addTextOnImage() {
  try {
    const width = 750;
    const height = 483;
    const text = "Sammy the Shark";

    const svgImage = `
    <svg width="${width}" height="${height}">
    </svg>
    `;
  } catch (error) {
    console.log(error);
  }
}

addTextOnImage();

addTextOnImage() 函数定义了四个变量:width、height、text 和 svgImage。 width 保存整数750,height 保存整数483。 text 保存字符串Sammy the Shark。 这是您将使用 SVG 绘制的文本。

svgImage 变量保存 svg 元素。 svg 元素有两个属性:width 和 height,它们插入您之前定义的宽度和高度变量。 svg 元素根据给定的宽度和高度创建一个透明的容器。

您为 svg 元素提供了 750 的宽度和 483 的高度,以便 SVG 图像具有与 sammy.png 相同的大小。 这将有助于使文本看起来以 sammy.png 图像为中心。

接下来,您将绘制文本图形。 添加突出显示的代码以在 SVG 容器上绘制 Sammy the Shark:

async function addTextOnImage() {
    ...
    const svg = `
    <svg width="${width}" height="${height}">
    <text x="50%" y="50%" text-anchor="middle" class="title">${text}</text>
    </svg>
    `;
  ....
}

SVG 文本元素有四个属性:x、y、text-anchor 和 class。 x 和 y 定义您在 SVG 容器上绘制的文本的位置。 x 属性水平放置文本,y 属性垂直放置文本。

将 x 设置为 50% 将在 x 轴上的容器中间绘制文本,将 y 设置为 50% 将文本定位在 SVG 图像的 y 轴上的中间。

文本锚将文本水平对齐。 将 text-anchor 设置为 middle 将使文本在您指定的 x 坐标的中心对齐。

class 在文本元素上定义了一个类名。 您将使用类名将 CSS 样式应用于文本元素。

${text} 插入存储在 text 变量中的字符串 Sammy the Shark。 这是将在 SVG 图像上绘制的文本。

接下来,添加突出显示的代码以使用 CSS 设置文本样式:

const svg = `
<svg width="${width}" height="${height}">
  <style>
  .title { fill: #001; font-size: 70px; font-weight: bold;}
  </style>
  <text x="50%" y="50%" text-anchor="middle" class="title">${text}</text>
</svg>
`;

在这段代码中,fill 将文本颜色更改为黑色, font-size 更改字体大小, font-weight 更改字体粗细。

至此,您已经编写了使用 SVG 绘制文本 Sammy the Shark 所需的代码。 接下来,您将 SVG 图像保存为具有清晰的 png,以便您可以看到 SVG 如何绘制文本。 完成后,您将使用 sammy.png 合成 SVG 图像。

添加突出显示的代码以将 SVG 图像保存为具有清晰的 png:

....
    const svgImage = `
    <svg width="${width}" height="${height}">
    ...
    </svg>
    `;
    const svgBuffer = Buffer.from(svgImage);
    const image = await sharp(svgBuffer).toFile("svg-image.png");
  } catch (error) {
    console.log(error);
  }
}

addTextOnImage();

Buffer.from() 从 SVG 图像创建一个 Buffer 对象。 缓冲区是内存中存储二进制数据的临时空间。

创建缓冲区对象后,您可以使用缓冲区对象作为输入创建一个清晰的实例。 除了图像路径,sharp 还接受缓冲区、Uint9Array 或 Uint8ClampedArray。

最后,在项目目录中将 SVG 图像保存为 svg-image.png。

这是完整的代码:

const sharp = require("sharp");

async function addTextOnImage() {
  try {
    const width = 750;
    const height = 483;
    const text = "Sammy the Shark";

    const svgImage = `
    <svg width="${width}" height="${height}">
      <style>
      .title { fill: #001; font-size: 70px; font-weight: bold;}
      </style>
      <text x="50%" y="50%" text-anchor="middle" class="title">${text}</text>
    </svg>
    `;
    const svgBuffer = Buffer.from(svgImage);
    const image = await sharp(svgBuffer).toFile("svg-image.png");
  } catch (error) {
    console.log(error);
  }
}

addTextOnImage()

保存文件,并执行以下命令:

node addTextOnImage.js

在本地机器上打开 svg-image.png。 您现在应该看到文本 Sammy the Shark 呈现的透明背景:

image.png

现在您已经确认 SVG 代码绘制了文本,您将把文本图形合成到 sammy.png 上。

添加以下突出显示的代码以将 SVG 文本图形图像合成到 sammy.png 图像上。

const sharp = require("sharp");

async function addTextOnImage() {
  try {
    const width = 750;
    const height = 483;
    const text = "Sammy the Shark";

    const svgImage = `
    <svg width="${width}" height="${height}">
      <style>
      .title { fill: #001; font-size: 70px; font-weight: bold;}
      </style>
      <text x="50%" y="50%" text-anchor="middle" class="title">${text}</text>
    </svg>
    `;
    const svgBuffer = Buffer.from(svgImage);
    const image = await sharp("sammy.png")
      .composite([
        {
          input: svgBuffer,
          top: 0,
          left: 0,
        },
      ])
      .toFile("sammy-text-overlay.png");
  } catch (error) {
    console.log(error);
  }
}

addTextOnImage();

Composite() 方法从 svgBuffer 变量中读取 SVG 图像,并将其放置在距 sammy.png 顶部 0 像素和左边缘 0 像素的位置。 接下来,将合成图像保存为 sammy-text-overlay.png。

保存的文件,并执行以下命令:

node addTextOnImage.js

在本地机器上打开 sammy-text-overlay.png。 您应该会看到在图像上添加的文本:

image.png

您现在已经使用composite() 方法在另一个图像上添加使用 SVG 创建的文本。

结论

在本文中,您学习了如何在 Node.js 中使用sharp的方法处理图像。 首先,您创建了一个实例来读取图像并使用 metadata() 方法提取图像元数据。 然后使用 resize() 方法调整图像大小。 之后,您使用 format() 方法更改图像类型,并压缩图像。 接下来,您继续使用各种sharp的方法来裁剪、灰度、旋转和模糊图像。 最后,您使用了 composite() 方法来合成图像,并在图像上添加文本。

要更深入地了解其他sharp的方法,请访问 sharp 文档

本文翻译的原文链接: https://www.digitalocean.com/community/tutorial_series/how-to-code-in-node-js

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容