Html转图片的那些事

最近遇到一些关于图片流操作的问题,就总结一下吧。

Html代码转图片并裁剪

找了很多jar都不尽如意。

jar包:html2image

错误的jar地址。http://mvnrepository.com/artifact/com.github.xuwei-k/html2image/0.1.0

正确的jar地址。https://github.com/e-ucm/eadventure/tree/master/etc/repository/gui/ava/html2image

方法调用:

  private static List<BufferedImage> resizeToHeight(BufferedImage image) {
    int width = image.getWidth();
    int height = image.getHeight() / 20;

    List<BufferedImage> images = Lists.newArrayList();

    IntStream.range(0, 20).forEach(i -> {
      BufferedImage ret = image.getSubimage(0, i * height, width, height);
      images.add(ret);
    });
    return images;
  }

  public static void main(String[] args) throws IOException {
    String htmlTxt = "<p style=\"text-align:center;\"><span style=\"font-size:15.0pt;\">0702-13</span></p>\n" +
        "\n" +
        "<p style=\"text-align:center;\"><span style=\"font-size:15.0pt;\">尺码:SML</span></p>\n" +
        "\n" +
        "<p style=\"text-align:center;\"><span style=\"font-family: lisu;\"><span style=\"color: rgb(69, 129, 142);\"><strong><span style=\"font-size: 24px;\">面料;衬衣弹力棉+黑色带弹力西装棉&nbsp;</span></strong></span></span><br />\n" +
        " <span style=\"font-size:15.0pt;\">尺码;SML</span><br />\n" +
        " <span style=\"font-size:15.0pt;\">小码;衬衣 胸围84 衣长59 肩34 袖56 半裙;裤腰66 臀围84 裙长50 马甲随衬衣尺寸</span><br />\n" +
        " <span style=\"font-size:15.0pt;\">中码;衬衣 胸围88 衣长60 肩35 袖57 半裙;裤腰70 臀围88 裙长51 马甲随衬衣尺寸</span><br />\n" +
        " <span style=\"font-size:15.0pt;\">大码;衬衣 胸围88 衣长60 肩36 袖58 半裙;裤腰74 臀围92 裙长52 马甲随衬衣尺寸</span></p>"+
        "<p><img alt=\"undefined\" src=\"https://img.alicdn.com/imgextra/i1/93733667/TB2V0xIt9FjpuFjSspbXXXagVXa_!!93733667.jpg\" /><br />\n" +
        " <img alt=\"undefined\" src=\"https://img.alicdn.com/imgextra/i4/93733667/TB2szRMt88kpuFjSspeXXc7IpXa_!!93733667.jpg\" /><br />\n" +
        " <img alt=\"undefined\" src=\"https://img.alicdn.com/imgextra/i3/93733667/TB2tZOqt9FjpuFjSszhXXaBuVXa_!!93733667.jpg\" /><br />\n" +
        " <img alt=\"undefined\" src=\"https://img.alicdn.com/imgextra/i3/93733667/TB2j4EvtMNlpuFjy0FfXXX3CpXa_!!93733667.jpg\" /><br />\n" +
        " <img alt=\"undefined\" src=\"https://img.alicdn.com/imgextra/i4/93733667/TB2RAsStHtlpuFjSspfXXXLUpXa_!!93733667.jpg\" /><br />\n" +
        " <img alt=\"undefined\" src=\"https://img.alicdn.com/imgextra/i4/93733667/TB2uYAZtR0kpuFjy1zdXXXuUVXa_!!93733667.jpg\" /><br />\n" +
        " <img alt=\"undefined\" height=\"384.8481308411215\" src=\"https://img.alicdn.com/imgextra/i3/93733667/TB2rPRMt88kpuFjSspeXXc7IpXa_!!93733667.jpg\" width=\"790\" /><br />\n" +
        " <img alt=\"undefined\" src=\"https://img.alicdn.com/imgextra/i4/93733667/TB2iyITtHXlpuFjSszfXXcSGXXa_!!93733667.jpg\" /><br />\n" +
        " <img alt=\"undefined\" src=\"https://img.alicdn.com/imgextra/i3/93733667/TB2KOITtHXlpuFjSszfXXcSGXXa_!!93733667.jpg\" /><br />\n" +
        " <img alt=\"undefined\" src=\"https://img.alicdn.com/imgextra/i2/93733667/TB27wBetYJkpuFjy1zcXXa5FFXa_!!93733667.jpg\" /><br />\n" +
        " <img alt=\"undefined\" src=\"https://img.alicdn.com/imgextra/i1/93733667/TB2dpJFt80kpuFjSsppXXcGTXXa_!!93733667.jpg\" /><br />\n" +
        " <img alt=\"undefined\" src=\"https://img.alicdn.com/imgextra/i4/93733667/TB2qQ7FtNXlpuFjSsphXXbJOXXa_!!93733667.jpg\" /><br />\n" +
        " <img alt=\"undefined\" src=\"https://img.alicdn.com/imgextra/i3/93733667/TB2m7wStHtlpuFjSspfXXXLUpXa_!!93733667.jpg\" /><br />\n" +
        " <img alt=\"undefined\" src=\"https://img.alicdn.com/imgextra/i4/93733667/TB2.odstYXlpuFjy1zbXXb_qpXa_!!93733667.jpg\" /><br />\n" +
        " <img alt=\"undefined\" src=\"https://img.alicdn.com/imgextra/i2/93733667/TB2fGcOtH0kpuFjy0FjXXcBbVXa_!!93733667.jpg\" /><br />\n" +
        " <img alt=\"undefined\" src=\"https://img.alicdn.com/imgextra/i4/93733667/TB2RyIltRNkpuFjy0FaXXbRCVXa_!!93733667.jpg\" /></p>";
    HtmlImageGenerator imageGenerator = new HtmlImageGenerator();
    imageGenerator.loadHtml(htmlTxt);
    BufferedImage imageNew = imageGenerator.getBufferedImage();
    
    List<BufferedImage> imageList = resizeToHeight(imageNew);

    for (int i = 0; i < imageList.size(); i++) {
      File outFile = new File("/Users/lxc/Desktop/temp/" + i + ".png");
      ImageIO.write(imageList.get(i), "png", outFile);
    }
  }

第一个方法为裁剪图片,并未对原来的图片进行修改。而是重新创建了一个 BufferedImage 对象

    /**
     * Returns a subimage defined by a specified rectangular region.
     * The returned <code>BufferedImage</code> shares the same
     * data array as the original image.
     * @param x the X coordinate of the upper-left corner of the
     *          specified rectangular region
     * @param y the Y coordinate of the upper-left corner of the
     *          specified rectangular region
     * @param w the width of the specified rectangular region
     * @param h the height of the specified rectangular region
     * @return a <code>BufferedImage</code> that is the subimage of this
     *          <code>BufferedImage</code>.
     * @exception RasterFormatException if the specified
     * area is not contained within this <code>BufferedImage</code>.
     */
    public BufferedImage getSubimage (int x, int y, int w, int h) {
        return new BufferedImage (colorModel,
                                  raster.createWritableChild(x, y, w, h,
                                                             0, 0, null),
                                  colorModel.isAlphaPremultiplied(),
                                  properties);
    }

还有一个通过url拼接图片的方法:

  public static List<File> mergeAndCutPic(List<String> picList) {
    int length = picList.size();
    if (length < 1) {
      return null;
    }
    List<File> files = Lists.newArrayList();
    try {
      BufferedImage[] images = new BufferedImage[length];
      int[][] imageArr = new int[length][];

      for (int i = 0; i < length; i++) {
        images[i] = ImageIO.read(new URL(picList.get(i)).openStream());
        int width = images[i].getWidth();
        int height = images[i].getHeight();
        imageArr[i] = new int[width * height];
        imageArr[i] = images[i].getRGB(0, 0, width, height, imageArr[i], 0, width);
      }

      int tempHeight = 0;
      int tempWidth = images[0].getWidth();

      for (BufferedImage image : images) {
        tempWidth = tempWidth > image.getWidth() ? tempWidth : image.getWidth();
        tempHeight += image.getHeight();
      }

      if (tempHeight < 1) {
        return null;
      }

      BufferedImage imageNew = new BufferedImage(tempWidth, tempHeight,
          BufferedImage.TYPE_INT_RGB);
      int height = 0;
      for (int i = 0; i < images.length; i++) {
        imageNew.setRGB(0, height, tempWidth, images[i].getHeight(), imageArr[i], 0, tempWidth);
        height += images[i].getHeight();
      }

      List<BufferedImage> imageList = resizeToHeight(imageNew);

      for (BufferedImage image : imageList) {
        File outFile = new File("/Users/lxc/Desktop/temp/"
            + System.currentTimeMillis() + UUID.randomUUID() + ".png");
        ImageIO.write(image, "png", outFile);
        files.add(outFile);
      }
    } catch (Exception e) {
      return null;
    }
    return files;
  }

注意要以png为后缀,以jpg结尾会全黑。

至于为啥,我.... 布吉岛。还没看源码,所以也很迷。


Html代码转图片(有中文)

用到一个 cssbox 的 jar。

文档,介绍等 http://cssbox.sourceforge.net/

Github: https://github.com/radkovo/CSSBox

Github的源码版本,pom依赖中的 jstyleparser 2.2-SNAPSHOT maven仓库么找到。只要2.1的。推荐看这个 源码

如果你使用maven或者gradle 配置如下

  compile group: 'net.sf.cssbox', name: 'cssbox', version: '4.12'
  compile group: 'net.sf.cssbox', name: 'jstyleparser', version: '2.1'
  compile group: 'net.sourceforge.nekohtml', name: 'nekohtml', version: '1.9.22'
  compile group: 'xml-apis', name: 'xml-apis', version: '1.4.01'
  compile group: 'xerces', name: 'xercesImpl', version: '2.11.0'

注意 xml-apis 不要换成新的版本了。

不然将会看到这个异常

ClassNotFoundException: org.w3c.dom.ElementTraversal

新版本已经淘汰这个接口。

转换方法调用我做了一些修改。

  public static List<File> renderURL(String url) throws Exception {
    if (!url.startsWith("http:") &&
        !url.startsWith("https:") &&
        !url.startsWith("ftp:") &&
        !url.startsWith("file:"))
      url = "http://" + url;

    DocumentSource docSource = new DefaultDocumentSource(url);
    DOMSource parser = new DefaultDOMSource(docSource);
    parser.setContentType("text/html; charset=utf-8");
    Document doc = parser.parse();

    MediaSpec media = new MediaSpec("screen");
    media.setDimensions(windowSize.width, windowSize.height);
    media.setDeviceDimensions(windowSize.width, windowSize.height);

    DOMAnalyzer da = new DOMAnalyzer(doc, docSource.getURL());
    da.setMediaSpec(media);
    da.attributesToStyles();
    da.addStyleSheet(null, CSSNorm.stdStyleSheet(), DOMAnalyzer.Origin.AGENT);
    da.addStyleSheet(null, CSSNorm.userStyleSheet(), DOMAnalyzer.Origin.AGENT);
    da.addStyleSheet(null, CSSNorm.formsStyleSheet(), DOMAnalyzer.Origin.AGENT);
    da.getStyleSheets();

    BrowserCanvas contentCanvas = new BrowserCanvas(da.getRoot(), da, docSource.getURL());
    contentCanvas.setAutoMediaUpdate(false);
    contentCanvas.getConfig().setClipViewport(false);
    contentCanvas.getConfig().setLoadImages(true);
    contentCanvas.getConfig().setLoadBackgroundImages(true);
    contentCanvas.createLayout(windowSize);

    docSource.close();
    return resizeToHeight(contentCanvas.getImage());
  }

如果么有这句话 parser.setContentType("text/html; charset=utf-8"); 中文将会乱码。

如果在服务器上发现会乱码而本地测试不会。可能是因为服务器没有中文的字体

https://juejin.im/entry/576e0c79816dfa0055d2f017


Html代码转图片后的压缩

因为淘宝的详情总图片大小最多 2.5m

所以需要进行压缩。

图片压缩使用了一个jar

compile 'net.coobird:thumbnailator:0.4.8'

可以根据比例,高度等进行压缩,质量还是可以的。

具体用法自行看源码吧 上一个Util的代码。

package mbxc.util;

import com.google.common.collect.Lists;
import cz.vutbr.web.css.MediaSpec;
import lombok.extern.slf4j.Slf4j;
import net.coobird.thumbnailator.Thumbnails;
import org.fit.cssbox.css.CSSNorm;
import org.fit.cssbox.css.DOMAnalyzer;
import org.fit.cssbox.io.DOMSource;
import org.fit.cssbox.io.DefaultDOMSource;
import org.fit.cssbox.io.DefaultDocumentSource;
import org.fit.cssbox.io.DocumentSource;
import org.fit.cssbox.layout.BrowserCanvas;
import org.w3c.dom.Document;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.List;
import java.util.UUID;
import java.util.stream.IntStream;

/**
 * Created by LXC on 2017/5/10.
 */
@Slf4j
public class ImageUtils {

  public static final Integer WIDTH = 750;

  private static final Dimension windowSize = new Dimension(750, 600);

  /**
   * 读取图片
   *
   * @param imgUrl
   * @throws IOException
   */
  public static BufferedImage createImage(String imgUrl) throws IOException {
    return ImageIO.read(new URL(imgUrl).openStream());
  }

  /**
   * 以宽度为基准,等比例放缩图片
   *
   * @param w int 新宽度
   */
  public static File resizeByWidth(BufferedImage img, int w) throws IOException {
    int width = img.getWidth();    // 得到源图宽
    int height = img.getHeight();  // 得到源图长
    int h = (height * w / width);
    return resize(img, w, h);
  }

  /**
   * 以高度为基准,等比例缩放图片
   *
   * @param h int 新高度
   */
  public static File resizeByHeight(BufferedImage img, int h) throws IOException {
    int width = img.getWidth();    // 得到源图宽
    int height = img.getHeight();  // 得到源图长
    int w = (width * h / height);
    return resize(img, w, h);
  }

  /**
   * 强制压缩/放大图片到固定的大小
   *
   * @param w int 新宽度
   * @param h int 新高度
   */
  private static File resize(BufferedImage img, int w, int h) throws IOException {
    String filePath = "/data/static/image/newFile.jpg";
    File file = new File(filePath);
    if (!file.exists()) {
      file.createNewFile();
    }
    Thumbnails.of(img).size(w, h).toFile(file);
    return file;
  }

  public static List<File> generateImage(String htmlPath) {
    try {
      return renderURL(htmlPath);
    } catch (Exception e) {
      return null;
    }
  }

  public static List<File> mergeAndCutPic(List<String> picList) {
    int length = picList.size();
    if (length < 1) {
      return null;
    }
    List<File> files = Lists.newArrayList();
    try {
      BufferedImage[] images = new BufferedImage[length];
      int[][] imageArr = new int[length][];

      for (int i = 0; i < length; i++) {
        images[i] = ImageIO.read(new URL(picList.get(i)).openStream());
        int width = images[i].getWidth();
        int height = images[i].getHeight();
        imageArr[i] = new int[width * height];
        imageArr[i] = images[i].getRGB(0, 0, width, height, imageArr[i], 0, width);
      }

      int tempHeight = 0;
      int tempWidth = images[0].getWidth();

      for (BufferedImage image : images) {
        tempWidth = tempWidth > image.getWidth() ? tempWidth : image.getWidth();
        tempHeight += image.getHeight();
      }

      if (tempHeight < 1) {
        return null;
      }

      BufferedImage imageNew = new BufferedImage(tempWidth, tempHeight,
          BufferedImage.TYPE_INT_RGB);
      int height = 0;
      for (int i = 0; i < images.length; i++) {
        imageNew.setRGB(0, height, tempWidth, images[i].getHeight(), imageArr[i], 0, tempWidth);
        height += images[i].getHeight();
      }
      return resizeToHeight(imageNew);
    } catch (Exception e) {
      return null;
    }
  }

  private static List<File> resizeToHeight(BufferedImage image) throws IOException {
    int width = image.getWidth();
    int height = image.getHeight() / 20;

    List<BufferedImage> images = Lists.newArrayList();

    IntStream.range(0, 20).forEach(i -> images.add(image.getSubimage(0, i * height, width, height)));
    List<File> files = Lists.newArrayList();

    for (BufferedImage img : images) {
      File outFile = new File("/Users/lxc/Desktop/temp/"
          + System.currentTimeMillis() + UUID.randomUUID() + ".jpg");
      ImageIO.write(img, "jpg", outFile);
      Thumbnails.of(outFile).scale(0.5).toFile(outFile);
      files.add(outFile);
    }
    return files;
  }

  public static void main(String[] args) {
    generateImage("file:/Users/lxc/Downloads/test.html");
  }

  private static List<File> renderURL(String url) throws Exception {
    if (!url.startsWith("http:") &&
        !url.startsWith("https:") &&
        !url.startsWith("ftp:") &&
        !url.startsWith("file:"))
      url = "http://" + url;

    DocumentSource docSource = new DefaultDocumentSource(url);
    DOMSource parser = new DefaultDOMSource(docSource);
    parser.setContentType("text/html; charset=utf-8");
    Document doc = parser.parse();

    MediaSpec media = new MediaSpec("screen");
    media.setDimensions(windowSize.width, windowSize.height);
    media.setDeviceDimensions(windowSize.width, windowSize.height);

    DOMAnalyzer da = new DOMAnalyzer(doc, docSource.getURL());
    da.setMediaSpec(media);
    da.attributesToStyles();
    da.addStyleSheet(null, CSSNorm.stdStyleSheet(), DOMAnalyzer.Origin.AGENT);
    da.addStyleSheet(null, CSSNorm.userStyleSheet(), DOMAnalyzer.Origin.AGENT);
    da.addStyleSheet(null, CSSNorm.formsStyleSheet(), DOMAnalyzer.Origin.AGENT);
    da.getStyleSheets();

    BrowserCanvas contentCanvas = new BrowserCanvas(da.getRoot(), da, docSource.getURL());
    contentCanvas.setAutoMediaUpdate(false);
    contentCanvas.getConfig().setClipViewport(false);
    contentCanvas.getConfig().setLoadImages(true);
    contentCanvas.getConfig().setLoadBackgroundImages(true);
    contentCanvas.createLayout(windowSize);

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

推荐阅读更多精彩内容