首页 > 其他分享 >使用fabric.js框选图片区域定位标注图片内容

使用fabric.js框选图片区域定位标注图片内容

时间:2024-03-20 12:12:13浏览次数:14  
标签:canvas obj fabric value js vals active const 图片

仍然是 在图片上特定区域根据数值显示不同的颜色 的需求,过了这么久,svg图迟迟提供不了,考虑canvas方案。记录比较下canvas及各canvas框架的使用。

canvas

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>使用 JavaScript 在图像上选择区域</title>
  </head>
  <body>
    <div>
      <canvas id="canvas1"></canvas>
      <script>
        {
          const canvas = document.getElementById("canvas1");
          const ctx = canvas.getContext("2d");

          const image = new Image();
          image.src = "../images/1.jpg";

          image.onload = function () {
            canvas.width = image.width;
            canvas.height = image.height;
            ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
          };

          let startX;
          let startY;
          let width;
          let height;

          canvas.addEventListener("mousedown", function (event) {
            startX = event.offsetX;
            startY = event.offsetY;
          });

          canvas.addEventListener("mouseup", function (event) {
            if (startX !== undefined && startY !== undefined) {
              width = event.offsetX - startX;
              height = event.offsetY - startY;

              ctx.beginPath();
              ctx.rect(startX, startY, width, height);
              ctx.strokeStyle = "#FF0000";
              ctx.stroke();

              console.log(`选中区域的位置为 (${startX}, ${startY}),大小为 ${width} × ${height}`);
            }
            startX = undefined;
            startY = undefined;
            width = undefined;
            height = undefined;
          });
        }
      </script>
    </div>
  </body>
</html>

使用鼠标在图片上拖动来绘制矩形及其他图形区域

annotorious.js

https://github.com/annotorious/annotorious
<link rel="stylesheet" href="../plugins/annotorious-2.7.13/annotorious.min.css" />
<script src="../plugins/annotorious-2.7.13/annotorious.min.js"></script>
<script src="../plugins/annotorious-2.7.13/annotorious-toolbar.min.js"></script>
<style>
  #hallstatt {
    border: 1px solid red;
  }
</style>
<div id="toolbar"></div>
<img id="hallstatt" src="../images/1.jpg" />

<script>
  var sampleAnnotation = {
    "@context": "http://www.w3.org/ns/anno.jsonld",
    id: "#a88b22d0-6106-4872-9435-c78b5e89fede",
    type: "Annotation",
    body: [
      {
        type: "TextualBody",
        value: "It's Hallstatt in Upper Austria",
      },
      {
        type: "TextualBody",
        purpose: "tagging",
        value: "Hallstatt",
      },
      {
        type: "TextualBody",
        purpose: "tagging",
        value: "Upper Austria",
      },
    ],
    target: {
      selector: {
        type: "FragmentSelector",
        conformsTo: "http://www.w3.org/TR/media-frags/",
        value: "xywh=pixel:421,80,151,151",
      },
    },
  };

  window.onload = function () {
    var anno = Annotorious.init({
      image: "hallstatt",
      locale: "auto",
      allowEmpty: true,
    });

    Annotorious.Toolbar(anno, document.getElementById("toolbar"));

    //可以标注矩形和多边形区域,不能标注圆形区域,且添加的注释未显示在图片上
    anno.on("createAnnotation", function (annotation) {
      console.log(annotation.target, annotation.body);
    });
  };
</script>

fabric.js

<script src="../plugins/fabric.min.js"></script>
<link href="../plugins/bootstrap-5.1.3/css/bootstrap.min.css" rel="stylesheet" />
<script src="../plugins/jquery/jquery-3.3.1.js"></script>
<script src="../plugins/bootstrap-5.1.3/js/bootstrap.bundle.min.js"></script>
<body>
  <div class="row g-2">
    <div class="col-8" style="overflow: auto">
      <canvas id="example" style="border: solid 1px #ccc"></canvas>
      <canvas id="example_re" style="border: solid 1px #ccc"></canvas>
    </div>
    <div class="col-4">
      <ul class="nav nav-tabs mt-2" id="myTab">
        <li class="nav-item">
          <a class="nav-link active" data-bs-toggle="tab" data-bs-target="#shape-rect" style="cursor: pointer">矩形</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" data-bs-toggle="tab" data-bs-target="#shape-circle" style="cursor: pointer">圆形</a>
        </li>
      </ul>
      <div class="tab-content">
        <div class="tab-pane fade active show" id="shape-rect" role="tabpanel">
          <div class="row g-2">
            <div class="col-12">
              <div class="input-group">
                <span class="input-group-text">Left</span>
                <input type="number" class="form-control" value="0" onchange="setProp(this,'rect','left')" />
              </div>
            </div>
            <div class="col-12">
              <div class="input-group">
                <span class="input-group-text">Top</span>
                <input type="number" class="form-control" value="0" onchange="setProp(this,'rect','top')" />
              </div>
            </div>
            <div class="col-12">
              <div class="input-group">
                <span class="input-group-text">Width</span>
                <input type="number" class="form-control" value="80" onchange="setProp(this,'rect','width')" />
              </div>
            </div>
            <div class="col-12">
              <div class="input-group">
                <span class="input-group-text">Height</span>
                <input type="number" class="form-control" value="50" onchange="setProp(this,'rect','height')" />
              </div>
            </div>
            <div class="col-12">
              <div class="input-group">
                <span class="input-group-text">Name</span>
                <input type="text" class="form-control" value="" onchange="setProp(this,'rect','name')" />
              </div>
            </div>
          </div>
          <button class="btn btn-secondary mt-2" onclick="addRect()">新增矩形</button>
        </div>
        <div class="tab-pane fade" id="shape-circle" role="tabpanel">
          <div class="row g-2">
            <div class="col-12">
              <div class="input-group">
                <span class="input-group-text">Left</span>
                <input type="number" class="form-control" value="0" onchange="setProp(this,'circle','left')" />
              </div>
            </div>
            <div class="col-12">
              <div class="input-group">
                <span class="input-group-text">Top</span>
                <input type="number" class="form-control" value="0" onchange="setProp(this,'circle','top')" />
              </div>
            </div>
            <div class="col-12">
              <div class="input-group">
                <span class="input-group-text">radius</span>
                <input type="number" class="form-control" value="50" onchange="setProp(this,'circle','radius')" />
              </div>
            </div>
            <div class="col-12">
              <div class="input-group">
                <span class="input-group-text">LineWidth</span>
                <input type="number" class="form-control" value="5" onchange="setProp(this,'circle','strokeWidth')" />
              </div>
            </div>
            <div class="col-12">
              <div class="input-group">
                <span class="input-group-text">Name</span>
                <input type="text" class="form-control" value="" onchange="setProp(this,'circle','name')" />
              </div>
            </div>
          </div>
          <button class="btn btn-secondary mt-2" onclick="addCircle()">新增圆形</button>
        </div>
      </div>

      <hr />

      <button class="btn btn-secondary" onclick="cloneShape()">复制图形</button>
      <button class="btn btn-secondary" onclick="delShape()">删除图形</button>
      <button class="btn btn-secondary mt-2" onclick="getAllShape()">预览</button>

      <hr />

      <div>共<span id="info">0</span>个</div>
    </div>
  </div>

  <script>
    const canvas = new fabric.Canvas("example");
    //背景图
    canvas.setBackgroundImage("../images/2.jpg", canvas.renderAll.bind(canvas));

    const canvas_re = new fabric.StaticCanvas("example_re");
    canvas_re.setBackgroundImage("../images/2.jpg", canvas_re.renderAll.bind(canvas_re));

    //通用属性
    const default_prop = {
      transparentCorners: false, //选中时 控制手柄的样式
      borderColor: "green",
      cornerColor: "green",
      cornerSize: 5,
      lockRotation: true, //禁止旋转
      lockScalingFlip: true, //禁止缩放时翻转
      lockSkewingX: true, //禁止水平方向扭曲
      lockSkewingY: true, //禁止垂直方向扭曲
    };
    const default_vals_rect = [0, 0, 80, 50, ""];
    const default_vals_circle = [0, 0, 50, 5, ""];

    // 创建对象
    const bg_img = new Image();
    bg_img.src = "../images/2.jpg";
    bg_img.onload = function () {
      //将canvas的宽高设置为背景图片的宽高
      canvas.setWidth(bg_img.width);
      canvas.setHeight(bg_img.height);

      canvas_re.setWidth(bg_img.width);
      canvas_re.setHeight(bg_img.height);
    };

    //根据参数值或者表单值添加矩形区域
    function addRect(vals = [], is_re = false) {
      if (vals.length < 1) {
        $("#shape-rect input").each((idx, elem) => {
          if (idx < 4) {
            let value = parseInt(elem.value.trim());
            if (isNaN(value)) {
              elem.value = 10;
              value = 10;
            }
            vals.push(value);
          } else {
            let value = elem.value.trim();
            vals.push(value);
          }
        });
      }

      const rect = new fabric.Rect(
        Object.assign(
          {
            left: vals[0],
            top: vals[1],
            originX: "left",
            originY: "top",
            width: vals[2],
            height: vals[3],
            fill: "red",
          },
          default_prop
        )
      );
      rect["name"] = vals[4];
      cv = !is_re ? canvas : canvas_re;
      cv.add(rect);
      bindEvent(rect);
    }

    //根据参数值或者表单值添加圆形区域
    function addCircle(vals = [], is_re = false) {
      if (vals.length < 1) {
        $("#shape-circle input").each((idx, elem) => {
          if (idx < 4) {
            let value = parseInt(elem.value.trim());
            if (isNaN(value)) {
              elem.value = 10;
              value = 10;
            }
            vals.push(value);
          } else {
            let value = elem.value.trim();
            vals.push(value);
          }
        });
      }

      const circle = new fabric.Circle(
        Object.assign(
          {
            left: vals[0],
            top: vals[1],
            radius: vals[2],
            fill: "transparent",
            strokeWidth: vals[3],
            stroke: "red",
            lockScalingX: true,
            lockScalingY: true,
            lockScaling: true,
          },
          default_prop
        )
      );
      circle["name"] = vals[4];
      cv = !is_re ? canvas : canvas_re;
      cv.add(circle);
      bindEvent(circle);
    }

    //当单个对象选中或者修改时,将数值同步到表单区域
    function bindEvent(obj) {
      obj.on("selected", function () {
        setFormVal(obj);
      });
      obj.on("modified", function () {
        setFormVal(obj);
      });
    }

    //将当前对象的各项数值同步到表单区域
    function setFormVal(obj) {
      if (obj.type == "rect") {
        const triggerEl = document.querySelector(`#myTab a[data-bs-target="#shape-${obj.type}"]`);
        bootstrap.Tab.getOrCreateInstance(triggerEl).show();

        const vals = [obj.get("left"), obj.get("top"), obj.getScaledWidth(), obj.getScaledHeight(), obj.name];
        $("#shape-rect input").each((idx, elem) => {
          elem.value = vals[idx];
        });
      } else if (obj.type == "circle") {
        const triggerEl = document.querySelector(`#myTab a[data-bs-target="#shape-${obj.type}"]`);
        bootstrap.Tab.getOrCreateInstance(triggerEl).show();

        const vals = [obj.get("left"), obj.get("top"), obj.get("radius"), obj.get("strokeWidth"), obj.name];
        $("#shape-circle input").each((idx, elem) => {
          elem.value = vals[idx];
        });
      }
    }

    //将表单数值的变化 同步到canvas上
    function setProp(this_obj, shape, prop) {
      const active_objs = canvas.getActiveObjects();
      if (active_objs.length > 1) return;

      const active_obj = active_objs[0];
      if (active_obj instanceof fabric.Object) {
        const obj = {};
        obj[prop] = parseInt(this_obj.value);
        active_obj.set(obj);
        canvas.renderAll();
      }
    }

    //复制选中的对象
    function cloneShape() {
      let pos_x = 0;
      let pos_y = 0;
      //选择多个对象时需要重新计算坐标位置
      if (canvas.getActiveObject().type == "activeSelection") {
        const activeSelection = canvas.getActiveObject();
        pos_x = (activeSelection.left + (activeSelection.left + activeSelection.width)) / 2;
        pos_y = (activeSelection.top + (activeSelection.top + activeSelection.height)) / 2;
      }

      const active_objs = canvas.getActiveObjects();
      for (let i in active_objs) {
        const active_obj = active_objs[i];
        if (active_obj instanceof fabric.Object) {
          const left = pos_x + active_obj.get("left") + 20;
          const top = pos_y + active_obj.get("top") + 20;

          active_obj.clone((clone) => {
            Object.assign(clone, default_prop);
            clone["name"] = active_obj.name;
            clone["left"] = left;
            clone["top"] = top;

            if (active_obj.type == "circle") {
              clone["lockScalingX"] = true;
              clone["lockScalingY"] = true;
              clone["lockScaling"] = true;
            }

            canvas.add(clone);
            bindEvent(clone);
          });
        }
      }
    }

    //删除选中的对象
    function delShape() {
      const active_objs = canvas.getActiveObjects();
      for (let i in active_objs) {
        const active_obj = active_objs[i];
        if (active_obj instanceof fabric.Object) {
          canvas.remove(active_obj);
        }
      }
    }

    //获取canvas上的所有对象数据,在另外的canvas上重绘预览
    function getAllShape() {
      canvas.discardActiveObject();

      const data = [];
      const all_obj = canvas.getObjects();
      for (let i in all_obj) {
        const obj = all_obj[i];
        if (obj.type == "rect") {
          data.push({
            name: obj.name,
            type: obj.type,
            left: obj.get("left"),
            top: obj.get("top"),
            width: obj.getScaledWidth(),
            height: obj.getScaledHeight(),
          });
        } else if (obj.type == "circle") {
          data.push({
            name: obj.name,
            type: obj.type,
            left: obj.get("left"),
            top: obj.get("top"),
            radius: obj.get("radius"),
            strokeWidth: obj.get("strokeWidth"),
          });
        }
      }
      console.log("data", data);

      document.querySelector("#info").innerHTML = data.length;
      canvas_re.clear(); //清除所有(包括背景图)
      canvas_re.setBackgroundImage("../images/2.jpg", canvas_re.renderAll.bind(canvas_re));
      for (let i in data) {
        const obj = data[i];
        if (obj.type == "rect") {
          addRect([obj.left, obj.top, obj.width, obj.height, obj.name], true);
        } else if (obj.type == "circle") {
          addCircle([obj.left, obj.top, obj.radius, obj.strokeWidth, obj.name], true);
        }
      }

      // console.log(obj.type, obj.get("width"), obj.get("height"), obj.get("left"), obj.get("top"));
      // console.log(obj.getCenterPoint(), obj.getCoords(), obj.getBoundingRect(), obj.getObjectScaling(), obj.getPointByOrigin("left", "top"));
      // console.log(obj.getScaledHeight(), obj.getScaledWidth(), obj.id, obj.name);
    }

    //选中事件
    canvas.on("selection:created", function (opt) {
      console.log("selection:created", opt);
      //当选中多个对象时 不同步表单数据,只显示默认值
      if (opt.selected.length > 1) {
        $("#shape-rect input").each((idx, elem) => {
          elem.value = default_vals_rect[idx];
        });
        $("#shape-circle input").each((idx, elem) => {
          elem.value = default_vals_circle[idx];
        });
      }
    });
    //取消选中事件
    canvas.on("selection:cleared", function (opt) {
      $("#shape-rect input").each((idx, elem) => {
        elem.value = default_vals_rect[idx];
      });
      $("#shape-circle input").each((idx, elem) => {
        elem.value = default_vals_circle[idx];
      });
    });

    // 监听键盘事件,主要用于对齐位置
    document.addEventListener("keydown", function (event) {
      const active_objs = canvas.getActiveObjects();

      for (let i in active_objs) {
        const active_obj = active_objs[i];
        if (active_obj instanceof fabric.Object) {
          switch (event.keyCode) {
            case 37: // 左键
              active_obj.set({ left: active_obj.get("left") - 1 });
              // canvas.renderAll();
              break;
            case 38: // 上键
              active_obj.set({ top: active_obj.get("top") - 1 });
              // canvas.renderAll();
              break;
            case 39: // 右键
              active_obj.set({ left: active_obj.get("left") + 1 });
              // canvas.renderAll();
              break;
            case 40: // 下键
              active_obj.set({ top: active_obj.get("top") + 1 });
              // canvas.renderAll();
              break;
            default:
              break;
          }
        }
      }
      canvas.renderAll();
    });
  </script>
</body>

 

标签:canvas,obj,fabric,value,js,vals,active,const,图片
From: https://www.cnblogs.com/caroline2016/p/18084932

相关文章

  • js对象及new对象/函数的比较区别
    functionChenqiguo(){alert('qiguo');}Chenqiguo.prototype.getName=function(){alert('prototypeqiguo');}我们分别用new实例化构造函数和用普通的函数执行对原型求值varchenqiguo=newChenqiguo();//打印qiguochenqiguo.getName();//打印proto......
  • 基于Js和Java实现xlsx\xls文档的导入和下载
    基于Js和Java+MyBatis实现xlsx\xls文档的导入下载、导出背景:​ 实现xlsx\xls文档的导入、导出​ 导入效果:​ 导出效果:导出效果图1、导入、下载1.1、前台<divstyle="margin-left:15px"><inputtype="file"id="selectFile"name="selectFile"/>......
  • 将图片贴片到视频并播放3s
    首先用ffmeg获取视频的宽高为了使图片看起来和视频大小一致ffprobe-verror-select_streamsv:0-show_entriesstream=width,height-ofcsv=s=x:p=0xx1.mp4将图片缩放到视频宽高ffmpeg-itest.jpeg-vfscale=1200:750-update1test_xx1.jpeg将图片生成2s的视频......
  • C# 中使对象序列化/反序列化 Json 支持使用派生类型以及泛型的方式
    C#中使对象序列化/反序列化Json支持使用派生类型以及泛型方式废话#前言#为啥想写这个博客最近自己写的框架有用到这个类似工作流,支持节点编码自定义,动态运行自定义.尽量减少动态解析这就需要确定类型.有什么好的奇思妙想可以一起来讨论噢(现在还是毛坯,测......
  • package-lock.json
    生成package-lock.json文件:1、运行npminstall命令,npm将自动生成package-lock.json文件。2、如果你已经安装了依赖,但是没有生成package-lock.json文件:运行npminstall--save命令,这将会更新package-lock.json文件,并确保所有依赖项都被正确记录。3、如果你想要使用package-lock......
  • JSON Web Token 入门教程
    本文收录于Github.com/niumoo/JavaNotes,Java系列文档,数据结构与算法!本文收录于网站:https://www.wdbyte.com/,我的公众号:程序猿阿朗JSONWebToken(JWT)是一种可以在多方之间安全共享数据的开放标准,JWT数据经过编码和数字签名生成,可以确保其真实性,也因此JWT通常用于身份认证......
  • 4.零基础Nodejs快速入门——Node.js 模块化
    Node.js模块化一、介绍1.1什么是模块化与模块?将一个复杂的程序文件依据一定规则(规范)拆分成多个文件的过程称之为模块化其中拆分出的每个文件就是一个模块,模块的内部数据是私有的,不过模块可以暴露内部数据以便其他模块使用1.2什么是模块化项目?......
  • 6.零基础Nodejs快速入门——包管理工具
    包管理工具一、概念介绍1.1包是什么『包』英文单词是package,代表了一组特定功能的源码集合1.2包管理工具管理『包』的应用软件,可以对「包」进行下载安装,更新,删除,上传等操作借助包管理工具,可以快速开发项目,提升开发效率包管理工......
  • 2024中国行政区域含港澳台【省市区县镇乡村】五级联动地址json数据
    GitHub-657258535/China-Area-Region-Administrative-Divisions:中华人民共和国行政区划:省级(省份)、地级(城市)、县级(区县)、乡级(乡镇街道)、村级(村委会居委会)五级联动地址数据。 中华人民共和国行政区划:省级(省份)、地级(城市)、县级(区县)、乡级(乡镇街道)、村级(村委会居委会)......
  • es6关于class在js和ts中的的一些理解
    关于class在js和ts中的的一些理解js中:classHuman{//加上static该属性只能通过Human类去获取;//不加static该属性可以通过实例去获取其实等同于写在consturctor里面。只不过是写死的,不能自定义statichobby="吃饭";constructor(name,age){th......