65.9K
CodeProject 正在变化。 阅读更多。
Home

添加文本到图像 - 示例应用程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.87/5 (25投票s)

2016年3月6日

CPOL

6分钟阅读

viewsIcon

39014

一个小型应用程序,允许使用 ASP.NET MVC 和 jQuery 在图像上添加文本以进行标注或创建贺卡。

引言

该网站是一个单页 ASP.NET MVC 应用程序 (C#)。Entity Framework 用于将数据保存到 MS SQL 数据库。CRUD 操作使用 Web API 控制器执行。客户端的所有操作都使用 JavaScript 和 JQuery 执行。

为了从文本生成图像,使用了 Shao Voon Wong 提供的出色库 Outline Text

SVG 在图像操作方面发挥主要作用。

网站采用响应式设计。我没有依赖基于浏览器类型的 MVC 框架内置功能。元素根据其宽度在屏幕上排列。

网站只包含一个页面,该页面分为两个区域。当用户打开网站时,显示第一个区域: 

此区域用于上传源图像或从示例库中选择一个示例。因此用户有三个选项

  • 将图像拖放到带箭头的区域;
  • 从本地驱动器中选择图像;
  • 从示例库中选择一个示例;

图像上传或示例选择后,第一个区域隐藏,第二个区域显示

上传的图像位于屏幕右侧。左侧是用于添加文本和更改其设置的面板:字体大小、颜色、旋转等。

从文本生成图像的方法有很多选项:字体类型、颜色、描边颜色和粗细、阴影颜色和粗细等。将这些选项的控件放在页面上可能会让用户望而却步。为了减少它们的数量,创建了文本模板库。每个文本模板都包含所有这些属性,用户选择后只能更改主颜色和字体大小。

可以从剪贴画库添加的剪贴画将使图像更具吸引力。生成剪贴画图像的方法与生成文本图像的方法相同。剪贴画是特殊剪贴画字体的一个字符。 

用户可以通过点击“保存”按钮将生成的图像保存到本地文件系统。

运行代码

运行代码

  • 在 Visual Studio 2013 中打开解决方案。
  • 全部重新生成。
  • 将 AddTextToImage.WebUI 设置为启动项目。
  • 运行应用程序。

使用代码

代码包含在一个解决方案中,该解决方案是 Visual Studio 2013 解决方案,由五个项目组成

AddTextToImage.Data

数据访问层 - 包含仓库和 DbContextFactory 接口及其实现。 对于数据访问,正在使用 Entity Framework Code First 方法。

继承 DbContextDb 类具有每个实体所需的 DbSet<Entity>,符合 EF Code First 的要求。

DbContextFactory 类用于构造和获取 DbContext

Repository<T> 是一个通用仓库,它执行所有基本数据访问操作。

AddTextToImage.Domain 

项目 AddTextToImage.Domain 包含应用程序的 POCO 实体。下图显示了这些类

Entity 是所有 POCO 类的基类。ModelModelItem 表示源图像和从添加的文本生成的图像。 SampleSampleItem 类的目的是显示示例库。类 ClipartGalleryClipartTemplateTextGalleryTextTemplate 分别显示剪贴画库和文本模板库。ClipartGalleryTextGalleryClipartTemplateTextTemplate 具有相同的结构。但是,我更喜欢使用单独的类以便在数据库中拥有单独的表。

抽象类 TemplateBaseClipartTemplateTextTemplate 的基类。它似乎用作类 OutlineTextProcessor 构造函数的参数。在创建 OutlineTextProcessor 类时,其构造函数将 ClipartTemplateTextTemplate 作为参数。类 FontInfo 包含位于文件系统上的字体文件名。

AddTextToImage.ImageGenerator

它只有一个类 OutlineTextProcessor,它使用 TextDesignerCSLibrary.dll 库从文本生成图像。(上面提到了关于图像生成过程和此库的文章链接)。

AddTextToImage.UnitTests

项目包含一个类,其中包含几个控制器的单元测试。为此,我使用了 Visual Studio 单元测试框架和 Moq 库。

AddTextToImage.WebUI

正如我上面提到的,网站只有一个页面,因此项目中有一个名为 HomeController 的常规控制器。所有其他控制器都是 Web API,它们执行 CRUD 操作或返回生成的图像。

为了方便起见,我使用 jQuery 进行 DOM 操作。我还使用了 jQuery UI 的 Dialog 小部件来显示文本模板库和剪贴画库。这两个库直接从 CDN 网络加载。

所有 JavaScript 代码都在一个文件中:app.js。应用程序中使用的基本 JavaScript 对象列表如下表所示

JavaScript 对象 描述
textAsImage 顶级对象,包含所有以下对象。
errorMessage 当服务器在 AJAX 请求上返回错误时显示错误消息。
模型 保存源图像的属性。包含文本图像数组 (modelItems)。
modelItem 保存所有属性以从文本生成图像。
textSelector 表示文本模板库。
clipartSelector 表示剪贴画库。
sampleSelector  表示示例库。
fileUpload 将源图像上传到服务器。

下面我将描述 JavaScript 执行的主要操作

添加源图像

页面加载后,它在隐藏区域中包含一个空容器(SVG 元素)

<svg id="canvas" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" baseProfile="full" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 0 0">
</svg>

当用户选择源图像时,它通过 AJAX 请求保存到数据库中

// Upload source image to the server.
function uploadFile(files) {

    var data = new FormData();

    // Add the uploaded image content to the form data collection.
    if (files.length > 0) {

        disableControls();
        $("#file-placeholder").attr("src", basePath + "Content/Images/image-loading.gif");

        data.append("UploadedImage", files[0]);

        // AJAX request to upload source image to the server.
        $.ajax({
            type: "POST",
            url: basePath + "api/Model/UploadFile/",
            contentType: false,
            processData: false,
            data: data,

            success: function (data) {

                $("#select-image").remove();
                $("#image-worker").show();

                model.addModel(data.Id, data.ImageWidth, data.ImageHeight);
            },

            error: function (xhr, textStatus, errorThrown) {
                errorMessage.show(errorThrown);
            }
        });
    }
}

如果保存操作成功完成,页面的第一个区域将被删除,第二个区域将显示。

函数 addModel 将源图像添加到 SVG 容器

// Add source image.
function addModel(modelId, modelWidth, modelHeight) {

    // Save id, width and height of the source image.
    id = modelId;
    width = modelWidth;
    height = modelHeight;

    // Create SVG <image> element for source image.
    var svgSourceImg = document.createElementNS("http://www.w3.org/2000/svg", "image");
    svgSourceImg.setAttribute("id", "model" + id);
    svgSourceImg.setAttribute("height", "100%");
    svgSourceImg.setAttribute("width", "100%");
    svgSourceImg.setAttributeNS("http://www.w3.org/1999/xlink", "href", basePath + "api/Model/Image/" + id + "/");
    svgSourceImg.setAttribute("x", "0");
    svgSourceImg.setAttribute("y", "0");
    canvas.setAttribute("style", "margin-left: auto; margin-right: auto; max-width: " + modelWidth + "px;");
    canvas.setAttribute("viewBox", "0 0 " + modelWidth + " " + modelHeight);

    // Append image to SVG container
    canvas.appendChild(svgSourceImg);

    if (detectIE()) {

        var imgHeigth = modelHeight;

        if (modelWidth - $("#image-main").width() > 0) {

            imgHeigth = modelHeight * $("#image-main").width() / modelWidth;
        }

        $("#image-main").css("height", imgHeigth + "px");
    }

    // Set URL for downloading the resulting image.
    $("#form-save-result").attr("action", basePath + "api/Image/Result/" + id);

    // Set size for Delete image and thickness for bounding rectangle depending on the width of the source image.
    setDelImageSize();
}

结果是 SVG 容器具有 <image> 元素

<svg style="margin-left: auto; margin-right: auto; max-width: 1632px;" id="canvas" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" baseProfile="full" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1632 1224">
    <image y="0" x="0" xlink:href="/api/Model/Image/1424/" width="100%" height="100%" id="model1424"></image>
</svg>

添加文本

当用户输入文本并按下“添加文本”按钮时,会创建一个新的 modelItem 对象,并将其存储在数据库中。保存信息到数据库是后续生成输出图像所必需的

$("#btn-add-text").on("click", function () {

    if ($("#sample-text").val().length > 0) {

        // Create new text image.
        var modelItem = new ModelItem();
        modelItem.id = 0;
        modelItem.modelId = id;
        modelItem.itemType = 0;
        modelItem.text = $("#sample-text").val();
        modelItem.templateId = textSelector.getSelectedItemId();
        modelItem.fontSize = $("#font-size").val();
        modelItem.fontColor = $("#font-color").spectrum("get").toHexString();
        modelItem.rotation = $("#rotation").val();

        $.ajax({
            url: basePath + "api/Model/AddModelItem/",
            type: "PUT",
            dataType: "json",
            data: modelItem.getData(),

            success: function (modelItemId) {

                modelItem.id = modelItemId;

                addModelItem(modelItem);
            },

            error: function (xhr, textStatus, errorThrown) {
                errorMessage.show(errorThrown);
            }
        });
    }
});

如果保存操作成功完成,函数 addModelItem 会添加 HTML 代码以显示生成的图像: 

// Add text image.
function addModelItem(modelItem) {

    // Create SVG <g> element.
    var svgGroup = document.createElementNS("http://www.w3.org/2000/svg", "g");
    svgGroup.setAttribute("id", "img-group" + modelItem.id);

    // Create SVG <rect> element: bounding rectangle for the image
    var svgRect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
    svgRect.setAttribute("id", "rect" + modelItem.id);
    svgRect.setAttribute("x", modelItem.positionLeft);
    svgRect.setAttribute("y", modelItem.positionTop);
    svgRect.setAttribute("height", "0");
    svgRect.setAttribute("width", "0");
    svgRect.setAttribute("stroke", "red");
    svgRect.setAttribute("stroke-width", rectangleThickness);
    svgRect.setAttribute("stroke-dasharray", "5");
    svgRect.setAttribute("fill-opacity", "0.4");
    svgRect.setAttribute("fill", "none");
    svgRect.style.display = "none";

    svgGroup.appendChild(svgRect);

    // Create SVG <image> element for image generated from the text.
    var svgTextImg = document.createElementNS("http://www.w3.org/2000/svg", "image");
    svgTextImg.setAttribute("id", "img" + modelItem.id);
    svgTextImg.setAttribute("height", "0");
    svgTextImg.setAttribute("width", "0");
    svgTextImg.setAttributeNS("http://www.w3.org/1999/xlink", "href", basePath + "api/Image/ModelItem/" + modelItem.id + "/" + modelItem.getUrl());
    svgTextImg.setAttribute("x", modelItem.positionLeft);
    svgTextImg.setAttribute("y", modelItem.positionTop);
    svgTextImg.style.cursor = "move";

    svgGroup.appendChild(svgTextImg);

    // Create SVG <image> element for Delete image.
    var svgDelImg = document.createElementNS("http://www.w3.org/2000/svg", "image");
    svgDelImg.setAttribute("id", "del" + modelItem.id);
    svgDelImg.setAttributeNS("http://www.w3.org/1999/xlink", "href", basePath + "Content/Images/delete.png");
    svgDelImg.setAttribute("x", modelItem.positionLeft);
    svgDelImg.setAttribute("y", modelItem.positionTop - 16);
    svgDelImg.style.display = "none";
    svgDelImg.style.cursor = "pointer";
    svgDelImg.setAttribute("height", delImageSize);
    svgDelImg.setAttribute("width", delImageSize);

    svgGroup.appendChild(svgDelImg);

    // Add the whole group: bounding rectangle, image generated from the text and Delete image to canvas.
    canvas.appendChild(svgGroup);

    // Create hidden additional image. When it's loaded its width and height are set to svgRect and svgTextImg elements.
    var $hiddenImg = $("<img>", {
        id: "hidden-img" + modelItem.id,
        src: basePath + "api/Image/ModelItem/" + modelItem.id + "/" + modelItem.getUrl()
    });

    // Add created image to hidden area.
    $("#hidden-images").append($hiddenImg);

    // Attach events.
    $($hiddenImg).on("load", onLoadImage);
    $(svgDelImg).on("click", onClickDelete);
    $(svgTextImg).on("mousedown", onMoveStart);
    $(svgTextImg).on("mouseup", onMoveEnd);
    $(svgTextImg).on("click", onClickImage);
    $(svgTextImg).on("touchstart", onMoveStart);
    $(svgTextImg).on("touchend", onMoveEnd);

    // Add item to an array.
    modelItems.push(modelItem);

    // Select added item: bounding rectangle and Delete image are visible.
    selectItem(modelItem.id);
}

添加文本后,我们得到以下 HTML 输出

在此示例中,我们有一个带有源图像的 SVG 容器:id=model1424。为了显示生成的图像,使用了四个元素

<g id=img-group1666> - 将矩形和两个图像元素分组;
<rect id=rect1666> - 边界矩形。它显示图像已选中;
<image  id=img1666> - 从文本生成的图像;
<image  id=del1666> - 用作删除按钮;

图像移动

点击拖动功能分为以下事件:mousedownmousemovemouseupmmouseout 或用于触摸屏设备的 touchstarttouchmovetouchend。第一个是点击,当光标位于图像上方时按下鼠标左键或在图像上放置触摸点时触发

// mousedown and touchstart event handlers.
function onMoveStart(e) {

    // Needed for Firefox to allow dragging correctly
    e.preventDefault();

    if (e.type === "touchstart") {

        // Attach touchmove event handler
        $(e.target).on("touchmove", onMove);

        // Save the initial touch coordinates 
        mouseStart = getPoint(e.originalEvent.touches[0]);

    }
    else {

        // Attach mousemove and mouseout event handlers
        $(e.target).on("mousemove", onMove).on("mouseout", onMoveEnd);

        // Save the initial mouse coordinates
        mouseStart = getPoint(e);
    }

    // Save top and left position of the image.
    elementStart = {
        x: e.target["x"].animVal.value,
        y: e.target["y"].animVal.value
    };

    // Show bounding rectangle and Delete image. Set values for controls in Control Panel for selected modelItem.
    selectItem(e.target.id.substring(3));
}

处理图像移动的函数

// mousemove and touchmove event handlers.
function onMove(e) {

    // Get digital part of the image id.
    var id = e.target.id.substring(3);

    // Get current mouse or touch coordinates.
    var svgPoint = (e.type === "mousemove") ? getPoint(e) : getPoint(e.originalEvent.touches[0]);

    svgPoint.x = svgPoint.x - mouseStart.x;
    svgPoint.y = svgPoint.y - mouseStart.y;

    var m = e.target.getTransformToElement(canvas).inverse();

    m.e = m.f = 0;
    svgPoint = svgPoint.matrixTransform(m);

    // Set new position for image, bounding rectangle and Delete image.
    $("#img" + id).attr({
        "x": elementStart.x + svgPoint.x,
        "y": elementStart.y + svgPoint.y
    });
    $("#rect" + id).attr({
        "x": elementStart.x + svgPoint.x,
        "y": elementStart.y + svgPoint.y
    });
    $("#del" + id).attr({
        "x": elementStart.x + svgPoint.x + parseInt($("#img" + id).attr("width")),
        "y": elementStart.y + svgPoint.y - delImageSize
    });

    if (selectedItem != null) {

        selectedItem.positionLeft = Math.round(elementStart.x + svgPoint.x);
        selectedItem.positionTop = Math.round(elementStart.y + svgPoint.y);
    }
}

事件 mouseupmouseout 或 touchend 用于检测用户何时停止移动图像

// mouseup, mouseout and touchend event handlers.
function onMoveEnd(e) {

    if (e.type === "touchend") {

        // Detach touchmove event handler
        $(e.target).off("touchmove", onMove);
    }
    else {

        // Detach mousemove and mouseout event handlers
        $(e.target).off("mousemove", onMove).off("mouseout", onMoveEnd);
    }

    if (selectedItem != null) {
        selectedItem.updateDatabase();
    }
}

致谢

感谢 Shao Voon Wong 的文章:Outline TextOutline Text Part 2。他的库在此应用程序中发挥主要作用。

感谢 Rod Stephens 的文章 Rotate images by an arbitrary angle in C#,我在我的应用程序中使用了它。

感谢 Brian Grinstead 的 The No Hassle JavaScript Colorpicker。

历史

  • 06.03.2016 - 初始版本发布。 
© . All rights reserved.