[
  {
    "path": ".github/workflows/npmpublish.yml",
    "content": "name: Node.js Package\r\n\r\non:\r\n  push:\r\n    branches:\r\n      - master\r\n\r\njobs:\r\n  build-node-8:\r\n    runs-on: ubuntu-latest\r\n    steps:\r\n      - uses: actions/checkout@v2\r\n      - uses: actions/setup-node@v1\r\n        with:\r\n          node-version: 8\r\n      - run: npm ci\r\n      - run: npm test\r\n\r\n  build-node-10:\r\n    needs: build-node-8\r\n    runs-on: ubuntu-latest\r\n    steps:\r\n      - uses: actions/checkout@v2\r\n      - uses: actions/setup-node@v1\r\n        with:\r\n          node-version: 10\r\n      - run: npm ci\r\n      - run: npm test\r\n\r\n  build-node-12:\r\n    needs: build-node-10\r\n    runs-on: ubuntu-latest\r\n    steps:\r\n      - uses: actions/checkout@v2\r\n      - uses: actions/setup-node@v1\r\n        with:\r\n          node-version: 12\r\n      - run: npm ci\r\n      - run: npm test\r\n\r\n  publish-npm:\r\n    needs: build-node-12\r\n    runs-on: ubuntu-latest\r\n    steps:\r\n      - uses: actions/checkout@v1\r\n      - uses: actions/setup-node@v1\r\n        with:\r\n          node-version: 12\r\n          registry-url: https://registry.npmjs.org/\r\n      - run: npm ci\r\n      - run: npm publish\r\n        env:\r\n          NODE_AUTH_TOKEN: ${{secrets.npm_token}}\r\n\r\n"
  },
  {
    "path": ".gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (http://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# Typescript v1 declaration files\ntypings/\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variables file\n.env\n\n# my\n.vscode\nbuild\n*.heapsnapshot\n\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2017 ZhangYuan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# node-digital-watermarking\r\n[A digital watermark is a kind of marker covertly embedded in a noise-tolerant signal such as an audio, video or image data. It is typically used to identify ownership of the copyright of such signal. \"Watermarking\" is the process of hiding digital information in a carrier signal; the hidden information should, but does not need to, contain a relation to the carrier signal. Digital watermarks may be used to verify the authenticity or integrity of the carrier signal or to show the identity of its owners. It is prominently used for tracing copyright infringements and for banknote authentication.](https://en.wikipedia.org/wiki/Digital_watermarking)\r\n<br />\r\n[数字水印（Digital Watermarking）技术是将一些标识信息（即数字水印）直接嵌入数字载体当中（包括多媒体、文档、软件等）或是间接表示（修改特定区域的结构），且不影响原载体的使用价值，也不容易被探知和再次修改。但可以被生产方识别和辨认。通过这些隐藏在载体中的信息，可以达到确认内容创建者、购买者、传送隐秘信息或者判断载体是否被篡改等目的。数字水印是保护信息安全、实现防伪溯源、版权保护的有效办法，是信息隐藏技术研究领域的重要分支和研究方向。](https://baike.baidu.com/item/%E6%95%B0%E5%AD%97%E6%B0%B4%E5%8D%B0/722667)\r\n\r\n# package install\r\n```\r\nnpm install digital-watermarking\r\n```\r\nThis package is only used for the Node.js, if you are using Web, use [web-digital-watermarking](https://github.com/zy445566/web-digital-watermarking).\r\n\r\n# Sample Use\r\n```js\r\nconst dw = require('digital-watermarking');\r\n//EnCode Image add digital watermarking\r\nlet srcFileName = \"srcImg.png\";\r\nlet watermarkText = \"github.com/zy445566\";\r\nlet fontSize = 1.1;\r\nlet enCodeFileName = \"enCode.png\";\r\nasync function run() {\r\n    await dw.transformImageWithText(srcFileName,watermarkText,fontSize,enCodeFileName);\r\n    //DeCode Image get digital watermarking\r\n    let deCodeFileName = \"deCode.png\";\r\n    await dw.getTextFormImage(enCodeFileName,deCodeFileName);\r\n}\r\nrun()\r\n\r\n```\r\n\r\n# Result\r\n## enCode.png\r\n![enCode.png](https://raw.githubusercontent.com/zy445566/node-digital-watermarking/master/test/enCode.png)\r\n## deCode.png\r\n![deCode.png](https://raw.githubusercontent.com/zy445566/node-digital-watermarking/master/test/deCode.png)"
  },
  {
    "path": "index.d.ts",
    "content": "import Jimp from \"jimp/*\";\n\ndeclare class DigitalWatermarking {\n    static transformImageWithText(\n        srcFileName:string,watermarkText:string,\n        fontSize:number,enCodeFileName:string\n    ):Promise<Jimp>;\n    static transformImageBufferWithText(srcBuffer:Buffer,watermarkText:string,fontSize:number):Promise<Jimp>;\n    static getTextFormImage(enCodeFileName:string,deCodeFileName:string):Promise<Jimp>;\n    static getTextFormImageBuffer(enCodeBuffer:Buffer):Promise<Jimp>;\n}\nexport function transformImageWithText(\n    srcFileName:string,watermarkText:string,\n    fontSize:number,enCodeFileName:string\n):Promise<Jimp>;\n\nexport function transformImageBufferWithText(srcBuffer:Buffer,watermarkText:string,fontSize:number):Promise<Jimp>;\n\nexport function getTextFormImage(enCodeFileName:string,deCodeFileName:string):Promise<Jimp>;\n\nexport function getTextFormImageBuffer(enCodeBuffer:Buffer):Promise<Jimp>;"
  },
  {
    "path": "index.js",
    "content": "const lib = require('./lib.js');\nconst path = require('path');\nconst fs = require('fs');\nclass DigitalWatermarking{\n    static getConfig() {\n        return lib.getConfig()\n    }\n\n    static setConfig({opencvJsPath}) {\n        return lib.setConfig({opencvJsPath})\n    }\n\n    static getAbsoluteFilePath(filePath)\n    {\n        return path.isAbsolute(filePath)?filePath:path.join(process.cwd(),filePath);\n    }\n\n    static existsFilePath(filePath)\n    {\n        if(!fs.existsSync(filePath)){throw new Error(`not file in ${filePath}`);}\n    }\n    static isBuffer(fileBuf)\n    {\n        if((!(fileBuf instanceof Buffer))) {\n            throw new Error(`input not Buffer`);\n        }\n    }\n\n    static async transformImageWithText(srcFileName,watermarkText,fontSize,enCodeFileName)\n    {\n        const srcFilePath = DigitalWatermarking.getAbsoluteFilePath(srcFileName);\n        DigitalWatermarking.existsFilePath(srcFilePath);\n        return await lib.transformImageWithText(\n            srcFilePath,\n            watermarkText,fontSize,\n            DigitalWatermarking.getAbsoluteFilePath(enCodeFileName)\n        );\n    }\n\n    static async transformImageBufferWithText(srcBuffer,watermarkText,fontSize)\n    {\n        DigitalWatermarking.isBuffer(srcBuffer);\n        return await lib.transformImageWithText(\n            srcBuffer,\n            watermarkText,fontSize,\n        );\n    }\n\n    static async getTextFormImage(enCodeFileName,deCodeFileName)\n    {\n        const enCodeFilePath = DigitalWatermarking.getAbsoluteFilePath(enCodeFileName);\n        DigitalWatermarking.existsFilePath(enCodeFilePath);\n        return await lib.getTextFormImage(\n            enCodeFileName,\n            DigitalWatermarking.getAbsoluteFilePath(deCodeFileName)\n        );\n    }\n    static async getTextFormImageBuffer(enCodeBuffer) {\n        DigitalWatermarking.isBuffer(enCodeBuffer);\n        return await lib.getTextFormImage(\n            enCodeBuffer\n        );\n    }\n}\nmodule.exports = DigitalWatermarking;"
  },
  {
    "path": "lib.js",
    "content": "const Jimp = require('jimp');\nconst requireVm = require('require-vm');\n\nlet libConfig={\n  opencvJsPath:''\n};\n\nfunction getConfig() {\n  return libConfig\n}\nfunction setConfig(configData) {\n  libConfig = configData\n}\n\nfunction isReadyFunc () {\n    return new Promise((reslove,reject)=>{\n      const context = {\n        module:{exports:{}},\n        Module:{\n            onRuntimeInitialized() {\n              context.cv = context.module.exports();\n              const cv = context.cv;\n              cv.idft = function(src, dst, flags, nonzero_rows ) {\n                cv.dft( src, dst, flags | cv.DFT_INVERSE, nonzero_rows );\n              }\n              return reslove(context);\n            },\n            onAbort() {\n              return reject(new Error('loading opencv error'))\n            }\n        },\n        print:console.log\n      }\n      requireVm(libConfig.opencvJsPath || './opencv.js',context,{},{},true)\n    })\n}\n\nfunction shiftDFT(cv, mag) {\n    let rect = new cv.Rect(0, 0, mag.cols & (-2), mag.rows & (-2));\n    mag.roi(rect);\n\n    let cx = mag.cols / 2;\n    let cy = mag.rows / 2;\n\n    let q0 = mag.roi(new cv.Rect(0, 0, cx, cy));\n    let q1 = mag.roi(new cv.Rect(cx, 0, cx, cy));\n    let q2 = mag.roi(new cv.Rect(0, cy, cx, cy));\n    let q3 = mag.roi(new cv.Rect(cx, cy, cx, cy));\n\n    let tmp =  new cv.Mat();\n    q0.copyTo(tmp);\n    q3.copyTo(q0);\n    tmp.copyTo(q3);\n\n    q1.copyTo(tmp);\n    q2.copyTo(q1);\n    tmp.copyTo(q2);\n\n    tmp.delete()\n    q0.delete()\n    q1.delete()\n    q2.delete()\n    q3.delete()\n}\n\nfunction getBlueChannel(cv, image)\n{\n    let nextImg = image;\n    let channel = new cv.MatVector();\n    cv.split(nextImg, channel);\n    return channel.get(0);\n}\n\nfunction getDftMat(cv, padded)\n{\n    let planes = new cv.MatVector();\n    planes.push_back(padded);\n    let matZ = new cv.Mat.zeros(padded.size(), cv.CV_32F)\n    planes.push_back(matZ);\n    let comImg = new cv.Mat();\n    cv.merge(planes,comImg);\n    cv.dft(comImg, comImg);\n    matZ.delete();\n    return comImg;\n}\n\nfunction addTextByMat(cv, comImg,watermarkText,point,fontSize)\n{\n    cv.putText(comImg, watermarkText, point, cv.FONT_HERSHEY_DUPLEX, fontSize, cv.Scalar.all(0),2);  \n    cv.flip(comImg, comImg, -1);\n    cv.putText(comImg, watermarkText, point, cv.FONT_HERSHEY_DUPLEX, fontSize, cv.Scalar.all(0),2);  \n    cv.flip(comImg, comImg, -1);\n}\n\nfunction transFormMatWithText(cv, srcImg, watermarkText,fontSize) {\n    let padded = getBlueChannel(cv, srcImg);\n    padded.convertTo(padded, cv.CV_32F);\n    let comImg = getDftMat(cv, padded);\n    // add text \n    let center = new cv.Point(padded.cols/2, padded.rows/2);\n    addTextByMat(cv, comImg,watermarkText,center,fontSize);\n    let outer = new cv.Point (45, 45);\n    addTextByMat(cv, comImg,watermarkText,outer,fontSize);\n    //back image\n    let invDFT = new cv.Mat();\n    cv.idft(comImg, invDFT, cv.DFT_SCALE | cv.DFT_REAL_OUTPUT, 0);\n    let restoredImage = new cv.Mat();\n    invDFT.convertTo(restoredImage, cv.CV_8U);\n    let backPlanes = new cv.MatVector();\n    cv.split(srcImg, backPlanes);\n    // backPlanes.erase(backPlanes.get(0));\n    // backPlanes.insert(backPlanes.get(0), restoredImage);\n    backPlanes.set(0,restoredImage)\n    let backImage = new cv.Mat();\n    cv.merge(backPlanes,backImage);\n\n    padded.delete();\n    comImg.delete();\n    invDFT.delete();\n    restoredImage.delete()\n    return backImage;\n}\n\nfunction getTextFormMat(cv, backImage) {\n    let padded= getBlueChannel(cv, backImage);\n    padded.convertTo(padded, cv.CV_32F);\n    let comImg = getDftMat(cv, padded);\n    let backPlanes = new cv.MatVector();\n    // split the comples image in two backPlanes  \n    cv.split(comImg, backPlanes);\n    let mag = new cv.Mat();\n    // compute the magnitude\n    cv.magnitude(backPlanes.get(0), backPlanes.get(1), mag);\n    // move to a logarithmic scale  \n    let matOne = cv.Mat.ones(mag.size(), cv.CV_32F)\n    cv.add(matOne, mag, mag);  \n    cv.log(mag, mag);  \n    shiftDFT(cv, mag);\n    mag.convertTo(mag, cv.CV_8UC1);\n    cv.normalize(mag, mag, 0, 255, cv.NORM_MINMAX, cv.CV_8UC1);\n\n    padded.delete();\n    comImg.delete();\n    matOne.delete();\n    return mag;    \n}\n\nfunction matToBuffer(cv, mat){\n    if(!(mat instanceof cv.Mat)){\n        throw new Error(\"Please input the valid new cv.Mat instance.\");\n    }\n    var img=new cv.Mat();\n    var depth=mat.type()%8;\n    var scale=depth<=cv.CV_8S?1:depth<=cv.CV_32S?1/256:255;\n    var shift=depth===cv.CV_8S||depth===cv.CV_16S?128:0;\n    mat.convertTo(img,cv.CV_8U,scale,shift);\n    switch(img.type()){\n        case cv.CV_8UC1:cv.cvtColor(img,img,cv.COLOR_GRAY2RGBA);break;\n        case cv.CV_8UC3:cv.cvtColor(img,img,cv.COLOR_RGB2RGBA);break;\n        case cv.CV_8UC4:break;\n        default:throw new Error(\"Bad number of channels (Source image must have 1, 3 or 4 channels)\");\n    }\n    var imgData=Buffer.from(img.data);\n    img.delete()\n    return imgData\n}\n\nasync function transformImageWithText(srcFileName,watermarkText,fontSize,enCodeFileName='') {\n  const context = await isReadyFunc ()\n  const cv = context.cv;\n  if((typeof srcFileName)!='string' && (!(srcFileName instanceof Buffer))) {\n    throw new Error('fileName must be string or Buffer')\n  }\n  if((typeof watermarkText)!='string') {\n    throw new Error('waterMarkText must be string')\n  }\n  if((typeof fontSize)!='number') {\n    throw new Error('fontSize must be number')\n  }\n  if((typeof enCodeFileName)!='string') {\n    throw new Error('outFileName must be string')\n  }\n  let jimpSrc = await Jimp.read(srcFileName);\n  let srcImg = new cv.matFromImageData(jimpSrc.bitmap);\n  if (srcImg.empty()){throw new Error(\"read image failed\");}\n  let comImg = transFormMatWithText(cv, srcImg, watermarkText, fontSize);\n  const imgRes = new Jimp({\n    width: comImg.cols,\n    height: comImg.rows,\n    data: matToBuffer(cv, comImg)\n  });\n  srcImg.delete();\n  comImg.delete();\n  if(enCodeFileName) {\n    return await imgRes.writeAsync(enCodeFileName);\n  } else {\n    return imgRes\n  }\n}\n\nasync function getTextFormImage(enCodeFileName,deCodeFileName='') {\n    const context = await isReadyFunc ()\n    const cv = context.cv;\n    if((typeof enCodeFileName)!='string'  && (!(enCodeFileName instanceof Buffer))) {\n      throw new Error('fileName must be string or Buffer')\n    }\n    if((typeof deCodeFileName)!='string') {\n      throw new Error('backFileName must be string')\n    }\n\n    let jimpSrc = await Jimp.read(enCodeFileName);\n    let comImg = new cv.matFromImageData(jimpSrc.bitmap);\n    let backImage = getTextFormMat(cv, comImg);\n    const imgRes = await new Jimp({\n        width: backImage.cols,\n        height: backImage.rows,\n        data: matToBuffer(cv, backImage)\n    })\n    comImg.delete();\n    backImage.delete();\n    if(deCodeFileName) {\n      return await imgRes.writeAsync(deCodeFileName);\n    } else {\n      return imgRes\n    }\n  }\n\n\nmodule.exports.transformImageWithText = transformImageWithText;\nmodule.exports.getTextFormImage = getTextFormImage;\nmodule.exports.getConfig = getConfig;\nmodule.exports.setConfig = setConfig;"
  },
  {
    "path": "package.json",
    "content": "{\r\n  \"name\": \"digital-watermarking\",\r\n  \"version\": \"1.1.15\",\r\n  \"description\": \"A digital watermark is a kind of marker covertly embedded in a noise-tolerant signal such as an audio, video or image data. It is typically used to identify ownership of the copyright of such signal. \\\"Watermarking\\\" is the process of hiding digital information in a carrier signal; the hidden information should, but does not need to, contain a relation to the carrier signal. Digital watermarks may be used to verify the authenticity or integrity of the carrier signal or to show the identity of its owners. It is prominently used for tracing copyright infringements and for banknote authentication.\",\r\n  \"main\": \"index.js\",\r\n  \"scripts\": {\r\n    \"test\": \"node ./test/test.js\"\r\n  },\r\n  \"repository\": {\r\n    \"type\": \"git\",\r\n    \"url\": \"git+https://github.com/zy445566/node-digital-watermarking.git\"\r\n  },\r\n  \"keywords\": [\r\n    \"digital\",\r\n    \"watermark\",\r\n    \"embedded\",\r\n    \"hidden\"\r\n  ],\r\n  \"author\": \"zy445566\",\r\n  \"license\": \"MIT\",\r\n  \"gypfile\": true,\r\n  \"bugs\": {\r\n    \"url\": \"https://github.com/zy445566/node-digital-watermarking/issues\"\r\n  },\r\n  \"homepage\": \"https://github.com/zy445566/node-digital-watermarking#readme\",\r\n  \"dependencies\": {\r\n    \"@types/node\": \"^14.0.12\",\r\n    \"jimp\": \"^0.12.1\",\r\n    \"require-vm\": \"^1.0.8\"\r\n  }\r\n}\r\n"
  },
  {
    "path": "test/test.js",
    "content": "const dw = require('../index');\nconst path = require('path');\nconst fs = require('fs');\nconst Jimp = require('jimp');\nfunction getAbsolutePath(fileName) {\n    return path.join(__dirname,fileName)\n}\n//EnCode Image add digital watermarking\nlet srcFileName = getAbsolutePath(\"srcImg.png\");\nlet watermarkText = \"github.com/zy445566\";\nlet fontSize = 1.1;\nlet enCodeFileName = getAbsolutePath(\"enCode.png\");\nasync function run() {\n    dw.setConfig({opencvJsPath:path.join(__dirname,'../opencv.js')})\n    await dw.transformImageWithText(srcFileName,watermarkText,fontSize,enCodeFileName);\n    //DeCode Image get digital watermarking\n    let deCodeFileName = getAbsolutePath(\"deCode.png\");\n    await dw.getTextFormImage(enCodeFileName,deCodeFileName);\n\n    // let startTime = new Date().getTime();\n    // for(let i=0;i<1000;i++) {\n        const enCodeFileRes = await dw.transformImageBufferWithText(fs.readFileSync(srcFileName),watermarkText,fontSize);\n        const deCodeFileRes = await dw.getTextFormImageBuffer(fs.readFileSync(enCodeFileName));\n    //     console.log(enCodeFileRes instanceof Jimp,deCodeFileRes instanceof Jimp, i)\n    // }\n    // console.log('runTime',new Date().getTime()-startTime);\n    \n}\nrun()\n"
  }
]