How to Build Web Components for Adjusting the Parameters of Barcode Reading

Barcode reading is a complex task with different challenging scenarios. To suit most scenarios, Dynamsoft Barcode Reader provides rich image processing parameters which users can adjust.

There are two ways to adjust the parameters.

One is via the code:

// JavaScript example to set the barcode format to QR code
let settings = await scanner.getRuntimeSettings();
settings.barcodeFormatIds = Dynamsoft.DBR.EnumBarcodeFormat.BF_QR_CODE;
await scanner.updateRuntimeSettings(settings);

The other is via a JSON template:

{
  "ImageParameter":
    {
      "BarcodeFormatIds":["BF_QR_CODE"],
      "Description":"",
      "Name":"Settings"
    },
  "Version":"3.0"
}

You can learn more about the programming by reading this article and the architecture of Dynamsoft Barcode Reader by reading this article.

Since there are many parameters, a good user interface is needed for us to adjust them. In this article, we are going to build web components to do this with Stencil.js.

You can check out the online demo to see the final result.

Navigate through the barcode dataset site to learn about different kinds of images requiring specific parameters.

Build Web Components for Adjusting the Parameters

Let’s create a stencil project first and then create three web components for setting the barcode formats, modes of image processing and some general parameters.

New Project

Run the following command:

npm init stencil

You will be prompted to create a component project or an app project. Here, we choose component.

Barcode Formats Component

In the project, create a new component named barcode-formats.

npx stencil g barcode-formats

Define Barcode Formats

Create definitions.ts under src/components/barcode-formats for writing the enumerations of barcode formats. Here, we classify the enumerations into several kinds: one-dimensional barcode formats, two-dimensional barcode formats, other uncommon barcode formats and barcode format collections.

export enum Enum2DBarcodeFormat {
  BF_PDF417 = 33554432,
  BF_QR_CODE = 67108864,
  BF_DATAMATRIX = 134217728,
  BF_AZTEC = 268435456,
  BF_MAXICODE = 536870912,
  BF_MICRO_QR = 1073741824,
  BF_MICRO_PDF417 = 524288,
}

export enum Enum1DBarcodeFormat {
  BF_CODE_11 = 2097152,
  BF_CODE_39 = 1,
  BF_CODE_93 = 4,
  BF_CODE_128 = 2,
  BF_CODABAR = 8,
  BF_ITF = 16,
  BF_EAN_13 = 32,
  BF_EAN_8 = 64,
  BF_UPC_A = 128,
  BF_UPC_E = 256,
  BF_INDUSTRIAL_25 = 512,
  BF_CODE_39_EXTENDED = 1024,
  BF_MSI_CODE = 1048576,
  BF_GS1_DATABAR = 260096,
  BF_GS1_COMPOSITE = 2147483648,
  BF_PATCHCODE = 262144,
}

export enum EnumOtherBarcodeFormat {
  BF2_POSTALCODE = 32505856,
  BF2_NONSTANDARD_BARCODE = 1,
  BF2_USPSINTELLIGENTMAIL = 1048576,
  BF2_POSTNET = 2097152,
  BF2_PLANET = 4194304,
  BF2_AUSTRALIANPOST = 8388608,
  BF2_RM4SCC = 16777216,
  BF2_DOTCODE = 2,
  BF2_PHARMACODE_ONE_TRACK = 4,
  BF2_PHARMACODE_TWO_TRACK = 8,
  BF2_PHARMACODE = 12
}

export enum EnumBarcodeFormatCollection {
  BF_ALL = 4265607167,
  BF_ONED = 3147775,
  BF_NULL = 0,
  BF2_ALL = 4294967295,
  BF2_NULL = 0,
}

Add States

Add three barcode format option arrays as states for the UI to use.

@State() OneDBarcodeFormats: barcodeFormatOption[] = [];
@State() TwoDBarcodeFormats: barcodeFormatOption[] = [];
@State() OtherBarcodeFormats: barcodeFormatOption[] = [];

//in definition.ts
export interface barcodeFormatOption {
  name:string,
  enabled:boolean
}

Load the arrays when the component is loaded.

formatsArray = [];
componentWillLoad(){
  this.OneDBarcodeFormats = this.getOneDBarcodeFormats();
  this.TwoDBarcodeFormats = this.getTwoDBarcodeFormats();
  this.OtherBarcodeFormats = this.getOtherBarcodeFormats();
  this.formatsArray = [this.OneDBarcodeFormats,this.TwoDBarcodeFormats,this.OtherBarcodeFormats];
}

getOneDBarcodeFormats():barcodeFormatOption[] {
  return this.getFormatOptions(Enum1DBarcodeFormat);
}

getTwoDBarcodeFormats():barcodeFormatOption[] {
  return this.getFormatOptions(Enum2DBarcodeFormat);
}

getOtherBarcodeFormats():barcodeFormatOption[] {
  return this.getFormatOptions(EnumOtherBarcodeFormat);
}

getFormatOptions(formatEnum:any){
  let formats:barcodeFormatOption[] = [];
  for (var enumMember in formatEnum) {
    if (!Number.isInteger(parseInt(enumMember))) { //name
      formats.push({name:enumMember,enabled:true});
    }
  }
  return formats;
}

Render the UI for Specifying the Barcode Formats

Use checkboxes to check which barcode formats to use.

updateFormatStatus(e:any,formatOption:barcodeFormatOption){
  formatOption.enabled = e.target.checked;
}

formatNameForRead(name:string){
  name = name.replace("BF_","");
  name = name.replace("BF2_","");
  name = name.split("_").join(" ");
  return name;
}

render() {
  return (
    <Host>
      <div part="container">
        <div>
          <h2>1D</h2>
          {this.OneDBarcodeFormats.map(format => (
            <div class="barcodeFormat">
              <input type="checkbox" id={format.name} name={format.name} checked={format.enabled} onChange={(e:any) => (this.updateFormatStatus(e,format))}/>
              <label htmlFor={format.name}>{this.formatNameForRead(format.name)}</label>
            </div>
          ))}
        </div>
        <div>
          <h2>2D</h2>
          {this.TwoDBarcodeFormats.map(format => (
            <div class="barcodeFormat">
              <input type="checkbox" id={format.name} name={format.name} checked={format.enabled} onChange={(e:any) => (this.updateFormatStatus(e,format))}/>
              <label htmlFor={format.name}>{this.formatNameForRead(format.name)}</label>
            </div>
          ))}
        </div>
        <div>
          <h2>Other</h2>
          {this.OtherBarcodeFormats.map(format => (
            <div class="barcodeFormat">
              <input type="checkbox" id={format.name} name={format.name} checked={format.enabled} onChange={(e:any) => (this.updateFormatStatus(e,format))}/>
              <label htmlFor={format.name}>{this.formatNameForRead(format.name)}</label>
            </div>
          ))}
        </div>
      </div>
      <slot></slot>
    </Host>
  );
}

Load and Output the Settings

We can update the checkboxes by loading a setting object like the following (the current version of Dynamsoft Barcode Reader defines uncommon barcodes in BarcodeFormatIds_2) and output the settings to it as well:

{
  "BarcodeFormatIds": [
     "BF_EAN_13"
  ],
  "BarcodeFormatIds_2": [
    "BF2_POSTALCODE"
  ],
}

Define a method to load settings (it is a bit complex since we have to handle collection barcode formats like BF_ALL, which enables all the barcode formats):

@State() rerender:boolean = false; // for triggering rerendering
@Method()
async loadSettings(settings:any) 
{
  let enabledFormats:string[] = settings.BarcodeFormatIds ?? [];
  enabledFormats = enabledFormats.concat(settings.BarcodeFormatIds_2 ?? []);
  for (let i = 0; i < this.formatsArray.length; i++) {
    const formats = this.formatsArray[i];
    for (let j = 0; j < formats.length; j++) {
      const barcodeFormat = formats[j];      
      if (enabledFormats.indexOf(barcodeFormat.name) != -1) {
        barcodeFormat.enabled = true;
      }else{
        barcodeFormat.enabled = false;
      }
    }
  }
  for (let index = 0; index < enabledFormats.length; index++) {
    const enabledFormat = enabledFormats[index];
    const collectionEnumValue = EnumBarcodeFormatCollection[enabledFormat];
    if (collectionEnumValue === EnumBarcodeFormatCollection[enabledFormat]) {
      if (collectionEnumValue === EnumBarcodeFormatCollection.BF_ALL) {
        this.checkAllBarcodeFormats(false);
      }else if (collectionEnumValue === EnumBarcodeFormatCollection.BF_ONED) {
        this.checkAllOneDBarcodeFormats();
      }else if (collectionEnumValue === EnumBarcodeFormatCollection.BF2_ALL) {
        this.checkAllBarcodeFormats(true);
      }else if (collectionEnumValue === EnumBarcodeFormatCollection.BF_NULL) {
        this.uncheckAllBarcodeFormats(false);
      }else if (collectionEnumValue === EnumBarcodeFormatCollection.BF2_NULL) {
        this.uncheckAllBarcodeFormats(true);
      }
    }
  }
  this.rerender = !this.rerender;
}

checkAllBarcodeFormats(other:boolean){
  for (let i = 0; i < this.formatsArray.length; i++) {
    const formats = this.formatsArray[i];
    for (let j = 0; j < formats.length; j++) {
      if (other === false && i === 2) {
        return;
      }
      if (other === true && i != 2) {
        continue;
      }
      const barcodeFormat = formats[j];
      barcodeFormat.enabled = true;
    }
  }
}

checkAllOneDBarcodeFormats(){
  for (let j = 0; j < this.OneDBarcodeFormats.length; j++) {
    const barcodeFormat = this.OneDBarcodeFormats[j];
    barcodeFormat.enabled = true;
  }
}

uncheckAllBarcodeFormats(other:boolean){
  for (let i = 0; i < this.formatsArray.length; i++) {
    const formats = this.formatsArray[i];
    for (let j = 0; j < formats.length; j++) {
      if (other === false && i === 2) {
        return;
      }
      if (other === true && i != 2) {
        continue;
      }
      const barcodeFormat = formats[j];
      barcodeFormat.enabled = false;
    }
  }
}

Define a method to output the settings:

@Method()
async outputSettings():Promise<any> {
  let settings = {BarcodeFormatIds:[],BarcodeFormatIds_2:[]};
  for (let i = 0; i < this.formatsArray.length; i++) {
    const formats = this.formatsArray[i];
    for (let j = 0; j < formats.length; j++) {
      const format = formats[j];
      if (format.enabled) {
        if (i != 2) {
          settings.BarcodeFormatIds.push(format.name);
        }else{
          settings.BarcodeFormatIds_2.push(format.name);
        }
        
      }
    }
  }
  return settings;
}

Use the Component

  1. Add the component in HTML:

    <barcode-formats></barcode-formats>
    
  2. Load the settings from a template:

    let template = "{\"ImageParameter\":{\"BarcodeFormatIds\":[\"BF_QR_CODE\"],\"Description\":\"\",\"Name\":\"Settings\"},\"Version\":\"3.0\"}";
    let settings = JSON.parse(template);
    let barcodeFormats = document.querySelector("barcode-formats");
    barcodeFormats.loadSettings(settings.ImageParameter);
    
  3. Output the settings and update the original one:

    let template = "{\"ImageParameter\":{\"BarcodeFormatIds\":[\"BF_QR_CODE\"],\"Description\":\"\",\"Name\":\"Settings\"},\"Version\":\"3.0\"}";
    let settings = JSON.parse(template);
    let barcodeFormats = document.querySelector("barcode-formats");
    let formatOutput = await barcodeFormats.outputSettings();
    settings.ImageParameter.BarcodeFormatIds = formatOutput.BarcodeFormatIds;
    settings.ImageParameter.BarcodeFormatIds_2 = formatOutput.BarcodeFormatIds_2;
    

Image Processing Modes Component

In the project, create a new component named parameters-modes.

npx stencil g parameters-modes

The modes for controlling the image processing part can be adjusted with this component. You can see the complete list of modes in the docs.

One kind of image processing can have several modes. Each mode can have several arguments.

modes

The value of an argument can be boolean, number or string.

Each kind of image processing has a fixed length of modes. If the mode name contains skip, then it works as a placeholder.

Write Definitions

Create a definition.ts under src/components/parameters-modes for defining the image processing parameters.

export interface ImageprocessingParameterDef {
  name:string;
  modes:ModeDef[];
  skipName:string;
  length:number;
}

export interface ModeDef {
  name:"string";
  args:ModeArgumentDef[];
}

export interface ModeArgumentDef {
  name:string;
  type:"boolean"|"number"|"string";
  options?:string[];
  description?:string;
  max?:number;
  min?:number;
  default?:number;
}

Then, we can write a definition.json file for all the common image processing modes according to the definitions. With this, we can easily render the UI for adjusting the modes.

{
  "ImageProcessingParameters":[
    {
      "name":"BinarizationModes",
      "length":4,
      "skipName":"BM_SKIP",
      "modes":[
          {"name":"BM_SKIP","args":[]},
          {"name":"BM_THRESHOLD","args":[
            {
              "name":"BinarizationThreshold",
              "type":"number",
              "max":255,
              "min":-1,
              "default": -1
            },
            {
              "name":"ImagePreprocessingModesIndex",
              "type":"number",
              "max":3,
              "min":-1,
              "default": -1
            }
          ]},
          {"name":"BM_LOCAL_BLOCK","args":[
            {
              "name":"BlockSizeX",
              "type":"number",
              "max":1000,
              "min":0,
              "default": 0
            },
            {
              "name":"BlockSizeY",
              "type":"number",
              "max":1000,
              "min":0,
              "default": 0
            },
            {
              "name":"EnableFillBinaryVacancy",
              "type":"boolean",
              "default":1
            },
            {
              "name":"ThresholdCompensation",
              "type":"number",
              "max":255,
              "min":-255,
              "default": 10
            },
            {
              "name":"ImagePreprocessingModesIndex",
              "type":"number",
              "max":3,
              "min":-1,
              "default": -1
            }
          ]}
      ]
    },
    {
      "name":"LocalizationModes",
      "length":8,
      "skipName":"LM_SKIP",
      "modes":[
        {"name":"LM_SKIP","args":[]},
        {"name":"LM_CONNECTED_BLOCKS","args":[]},
        {"name":"LM_ONED_FAST_SCAN","args":[
          {
            "name":"ScanStride",
            "type":"number",
            "max":999,
            "min":0,
            "default": 0
          }, 
          {
            "name":"ScanDirection",
            "type":"number",
            "max":2,
            "min":0,
            "default": 0
          },
          {
            "name":"ConfidenceThreshold",
            "type":"number",
            "max":100,
            "min":0,
            "default": 60
          }
        ]},
        {"name":"LM_LINES","args":[]},
        {"name":"LM_SCAN_DIRECTLY","args":[
          {
            "name":"ScanStride",
            "type":"number",
            "max":999,
            "min":0,
            "default": 0
          }, 
          {
            "name":"ScanDirection",
            "type":"number",
            "max":2,
            "min":0,
            "default": 0
          },
          {
            "name":"IsOneDStacked",
            "type":"boolean",
            "max":1,
            "min":0,
            "default": 0
          }
        ]},
        {"name":"LM_STATISTICS","args":[]},
        {"name":"LM_STATISTICS_MARKS","args":[]},
        {"name":"LM_STATISTICS_POSTAL_CODE","args":[]},
        {"name":"LM_CENTRE","args":[]}
      ]
    },
    {
      "name":"ColourConversionModes",
      "length":2,
      "skipName":"CICM_SKIP",
      "modes":[
        {"name":"CICM_SKIP","args":[]},
        {"name":"CICM_GENERAL","args":[
          {
            "name":"BlueChannelWeight",
            "type":"number",
            "max":1000,
            "min":-1,
            "default": -1
          },
          {
            "name":"GreenChannelWeight",
            "type":"number",
            "max":1000,
            "min":-1,
            "default": -1
          },
          {
            "name":"RedChannelWeight",
            "type":"number",
            "max":1000,
            "min":-1,
            "default": -1
          }
        ]}
      ]
    },
    {
      "name":"DPMCodeReadingModes",
      "length":2,
      "skipName":"DPMCRM_SKIP",
      "modes":[
        {"name":"DPMCRM_SKIP","args":[]},
        {"name":"DPMCRM_GENERAL","args":[]}
      ]
    },
    {
      "name":"GrayscaleTransformationModes",
      "length":2,
      "skipName":"GTM_SKIP",
      "modes":[
        {"name":"GTM_SKIP","args":[]},
        {"name":"GTM_INVERTED","args":[]},
        {"name":"GTM_ORIGINAL","args":[]}
      ]
    },
    {
      "name":"ScaleUpModes",
      "length":4,
      "skipName":"SUM_SKIP",
      "modes":[
        {"name":"SUM_SKIP","args":[]},
        {"name":"SUM_AUTO","args":[]},
        {"name":"SUM_LINEAR_INTERPOLATION","args":[
          {
            "name":"AcuteAngleWithXThreshold",
            "type":"number",
            "max":90,
            "min":-1,
            "default": -1
          },
          {
            "name":"ModuleSizeThreshold",
            "type":"number",
            "max":999,
            "min":0,
            "default": 0
          },
          {
            "name":"TargetModuleSize",
            "type":"number",
            "max":10,
            "min":0,
            "default": 0
          }
        ]},
        {"name":"SUM_NEAREST_NEIGHBOUR_INTERPOLATION","args":[
          {
            "name":"AcuteAngleWithXThreshold",
            "type":"number",
            "max":90,
            "min":-1,
            "default": -1
          },
          {
            "name":"ModuleSizeThreshold",
            "type":"number",
            "max":999,
            "min":0,
            "default": 0
          },
          {
            "name":"TargetModuleSize",
            "type":"number",
            "max":10,
            "min":0,
            "default": 0
          }
        ]}
      ]
    },
    {
      "name":"TextureDetectionModes",
      "length":2,
      "skipName":"TDM_SKIP",
      "modes":[
        {"name":"TDM_SKIP","args":[]},
        {"name":"TDM_GENERAL_WIDTH_CONCENTRATION","args":[
          {
            "name":"Sensitivity",
            "type":"number",
            "max":9,
            "min":1,
            "default": 5
          }
        ]}
      ]
    },
    {
      "name":"ImagePreprocessingModes",
      "length":5,
      "skipName":"IPM_SKIP",
      "modes":[
        {"name":"IPM_SKIP","args":[]},
        {"name":"IPM_GENERAL","args":[]},
        {"name":"IPM_GRAY_EQUALIZE","args":[
            {
              "name":"Sensitivity",
              "type":"number",
              "max":9,
              "min":1,
              "default": 5
            }
          ]
        },
        {"name":"IPM_GRAY_SMOOTH","args":[
            {
              "name":"SmoothBlockSizeX",
              "type":"number",
              "max":1000,
              "min":3,
              "default": 3
            },
            {
              "name":"SmoothBlockSizeY",
              "type":"number",
              "max":1000,
              "min":3,
              "default": 3
            }
          ]
        },
        {"name":"IPM_SHARPEN_SMOOTH","args":[
            {
              "name":"SmoothBlockSizeX",
              "type":"number",
              "max":1000,
              "min":3,
              "default": 3
            },
            {
              "name":"SmoothBlockSizeY",
              "type":"number",
              "max":1000,
              "min":3,
              "default": 3
            },
            {
              "name":"SharpenBlockSizeX",
              "type":"number",
              "max":1000,
              "min":3,
              "default": 3
            },
            {
              "name":"SharpenBlockSizeY",
              "type":"number",
              "max":1000,
              "min":3,
              "default": 3
            }
          ]
        },
        {"name":"IPM_MORPHOLOGY","args":
          [
            {
              "name":"MorphShape",
              "type":"string",
              "options":["Rectangle","Cross","Ellipse"],
              "default":"Rectangle"
            },
            {
              "name":"MorphOperation",
              "type":"string",
              "options":["Erode","Dilate","Open","Close"],
              "default":"Close"
            },
            {
              "name":"MorphOperationKernelSizeX",
              "type":"number",
              "max":1000,
              "min":0,
              "default": 0
            },
            {
              "name":"MorphOperationKernelSizeY",
              "type":"number",
              "max":1000,
              "min":0,
              "default": 0
            }
          ]
        }
      ]
    },
    {
      "name":"BarcodeComplementModes",
      "length":2,
      "skipName":"BCM_SKIP",
      "modes":[
        {"name":"BCM_SKIP","args":[]},
        {"name":"BCM_GENERAL","args":[]}
      ]
    },
    {
      "name":"DeformationResistingModes",
      "length":6,
      "skipName":"DRM_SKIP",
      "modes":[
        {"name":"DRM_SKIP","args":[]},
        {"name":"DRM_AUTO","args":[]},
        {"name":"DRM_GENERAL","args":[
          {
            "name":"Level",
            "type":"number",
            "max":9,
            "min":1,
            "default": 5
          }
        ]},
        {"name":"DRM_BROAD_WARP","args":[]},
        {"name":"DRM_LOCAL_REFERENCE","args":[]},
        {"name":"DRM_DEWRINKLE","args":[]}
      ]
    }
  ]
}

We can use the following code to load the JSON file.

import { ImageprocessingParameterDef, ModeArgumentDef } from './definition';
import definition from "./definition.json";
export class ParametersModes {
  parametersDefinitions:ImageprocessingParameterDef[] = [];
  componentWillLoad(){
    this.parametersDefinitions = definition.ImageProcessingParameters as ImageprocessingParameterDef[];
  }
}

Render the UI for Adjusting the Image Processing Modes

Render parameters, render modes for each parameter and render arguments for each mode. The UI is defined based on the definition and the parameter object.

@State() parameters:any = {}; //object for the parameters

renderArgument(mode:any,arg:ModeArgumentDef,paramDef:ImageprocessingParameterDef){
  if (arg.type === "number") {
    return (
      <div>
        <label>
          {arg.name}
        </label>
        <input onChange={(event)=>this.handleArgument(event,arg,mode,paramDef)} type="range" min={arg.min ?? 0} max={arg.max ?? 1000} step="1" value={mode[arg.name] ?? arg.default}/>
        <input onChange={(event)=>this.handleArgument(event,arg,mode,paramDef)} type="number" min={arg.min ?? 0} max={arg.max ?? 1000} value={mode[arg.name] ?? arg.default}></input>
      </div>
    );
  }else if (arg.type === "boolean"){
    return (
      <div>
        <label>
          {arg.name}
        </label>
        <input onChange={(event)=>this.handleArgument(event,arg,mode,paramDef)} type="checkbox" checked={((mode[arg.name] ?? arg.default) === 0) ? false:true}></input>
      </div>
    );
  }else if (arg.type === "string"){
    return (
      <div>
        <label>
          {arg.name}
        </label>
        <select onChange={(event)=>this.handleArgument(event,arg,mode,paramDef)}>
          {arg.options.map(option => (
            <option value={option} selected={option === mode[arg.name]}>{option}</option>
          ))}
        </select>
      </div>
    );
  }
}

renderArguments(mode:any,paramDef:ImageprocessingParameterDef){
  let modeDef = this.getModeDef(paramDef,mode.Mode);
  if (modeDef) {
    return (
      <div class="arguments">
        {modeDef.args.map(arg => (
          this.renderArgument(mode,arg,paramDef)
        ))}
      </div>
    );
  }else{
    return "";
  }
}

renderOneMode(mode:any,paramDef:ImageprocessingParameterDef){
  return (
    <div>
      <select onInput={(event) => this.handleSelect(event,paramDef,mode)}>
      {paramDef.modes.map(modeDef => (
        <option value={modeDef.name} selected={modeDef.name === mode.Mode}>{modeDef.name}</option>
      ))}
      </select>
      {this.renderArguments(mode,paramDef)}
    </div>
  )
}

renderParameter(param:any,paraName:string){
  let paramDef = this.getParametertDef(paraName);
  this.AddSkip(param,paramDef);
  return (
    <div class="modes">
      {param.map(mode => (
        this.renderOneMode(mode,paramDef)
      ))}
    </div>
  )
}

renderParameters(){
  let paramNames = [];
  for (let param in this.parameters) {
    paramNames.push(param);
  }
  paramNames.sort();
  return (
    <div class="params">
      {paramNames.map(paraName => (
        <Fragment>
          <div>{paraName}</div>
          {this.renderParameter(this.parameters[paraName],paraName)}
        </Fragment>
      ))}
    </div>
  );
}

render() {
  return (
    <Host>
      {this.renderParameters()}
      <slot></slot>
    </Host>
  );
}

Auxiliary methods:

modifiedParams:{} = {}; //store which params are modified
handleSelect(event:any,paramDef:ImageprocessingParameterDef,mode:any){
  mode.Mode = event.target.value;
  let args = this.getModeDef(paramDef,mode.Mode).args;
  let argNames = [];
  for (let index = 0; index < args.length; index++) {
    const arg = args[index];
    argNames.push(arg.name);
  }
  let keysToDelete = [];
  for (let key in mode) {
    if (key != "Mode") {
      if (argNames.indexOf(key) === -1) {
        keysToDelete.push(key);
      }
    }
  }
  //reset the arguments in a mode if the mode is changed
  for (let index = 0; index < keysToDelete.length; index++) {
    const key = keysToDelete[index];
    delete mode[key]; 
  }

  this.dataModified(paramDef);
}

handleArgument(event:any,arg:ModeArgumentDef,mode:any,paramDef:ImageprocessingParameterDef) {
  let target = event.target;
  let value = parseInt(target.value);
  if (arg.type === "boolean") {
    mode[arg.name] = target.checked ? 1 : 0;;
  }else if (arg.type === "number") {
    mode[arg.name] = value;
  }else if (arg.type === "string") {
    mode[arg.name] = event.target.selectedOptions[0].value;
  }
  this.dataModified(paramDef);
}

dataModified(paramDef:ImageprocessingParameterDef){
  this.modifiedParams[paramDef.name] = true;
  this.rerender = !this.rerender;
}

//add skips if the length of modes in the settings does not meet the max length
AddSkip(param:any,paramDef:ImageprocessingParameterDef){
  let diff = paramDef.length - param.length;
  if (diff > 0) {
    for (let index = 0; index < diff; index++) {
      let mode = {Mode:paramDef.skipName};
      param.push(mode);
    }
  }
}

Load and Output the Settings

We can update the controls by loading a setting object like the following and output the settings to it as well:

{
  "ImageParameter" : {
    "BinarizationModes": [
      {
        "BlockSizeX": 0,
        "BlockSizeY": 0,
        "EnableFillBinaryVacancy": 1,
        "ImagePreprocessingModesIndex": -1,
        "LibraryFileName": "",
        "LibraryParameters": "",
        "Mode": "BM_LOCAL_BLOCK",
        "ThresholdCompensation": 10
      }
    ],
    "Name": "Settings"
  },
  "Version" : "3.0"
}

Define a method to load the settings:

@Method()
async loadSettings(params:any){
  this.modifiedParams = {};
  let paramsCopy = JSON.parse(JSON.stringify(params));
  let parametersNotSupported = [];
  for (let param in paramsCopy) {
    if (!this.getParametertDef(param)) {
      parametersNotSupported.push(param);
    }
  }
  parametersNotSupported.forEach(param => {
    delete paramsCopy[param];
  });
  this.addMissingSupportedParams(paramsCopy)
  this.parameters = paramsCopy
}

addMissingSupportedParams(paramsCopy:any){
  let paramNames = [];
  for (let param in paramsCopy) {
    paramNames.push(param);
  }
  for (let index = 0; index < this.parametersDefinitions.length; index++) {
    const paramDef = this.parametersDefinitions[index];
    if (paramNames.indexOf(paramDef.name) === -1) {
      paramsCopy[paramDef.name] = [];
    }
  }
}

Define a method to output the settings:

@Method()
async outputSettings(){
  let paramsCopy = JSON.parse(JSON.stringify(this.parameters));
  for (let key in paramsCopy) {
    let param = paramsCopy[key];
    this.RemoveSkip(param);
  }
  let paramsModified = {};
  for (let key in paramsCopy) {
    if (this.modifiedParams[key] === true)  {
      paramsModified[key] = paramsCopy[key];
    }
  }
  return paramsModified;
}

RemoveSkip(param:any){
  let skipRemoved = [];
  for (let index = 0; index < param.length; index++) {
    const mode = param[index];
    if (mode.Mode.indexOf("SKIP") === -1) {
      skipRemoved.push(mode);
    }
  }
  while (param.length != 0) {
    param.pop();
  }
  for (let index = 0; index < skipRemoved.length; index++) {
    param.push(skipRemoved[index]);
  }
}

Use the Component

  1. Add the component in HTML:

    <parameters-modes></parameters-modes>
    
  2. Load the settings from a template:

    let template = "{\"ImageParameter\":{\"BinarizationModes\":[{\"BlockSizeX\":0,\"BlockSizeY\":0,\"EnableFillBinaryVacancy\":1,\"ImagePreprocessingModesIndex\":-1,\"LibraryFileName\":\"\",\"LibraryParameters\":\"\",\"Mode\":\"BM_LOCAL_BLOCK\",\"ThresholdCompensation\":10}],\"Name\":\"Settings\"},\"Version\":\"3.0\"}";
    let settings = JSON.parse(template);
    let parametersModes = document.querySelector("parameters-modes");
    parametersModes.loadSettings(settings.ImageParameter);
    
  3. Output the settings and update the original one:

    let template = "{\"ImageParameter\":{\"BinarizationModes\":[{\"BlockSizeX\":0,\"BlockSizeY\":0,\"EnableFillBinaryVacancy\":1,\"ImagePreprocessingModesIndex\":-1,\"LibraryFileName\":\"\",\"LibraryParameters\":\"\",\"Mode\":\"BM_LOCAL_BLOCK\",\"ThresholdCompensation\":10}],\"Name\":\"Settings\"},\"Version\":\"3.0\"}";
    let settings = JSON.parse(template);
    let parametersModes = document.querySelector("parameters-modes");
    let params = await parametersModes.outputSettings();
    for (let key in params) {
      settings.ImageParameter[key] = params[key];
    }
    

General Parameters Component

There are other parameters which are useful.

Here is a list of them:

MinResultConfidence belongs to FormatSpecification. The others belong to ImageParameter.

Write Definitions

Similar to the way we do in the image processing modes component, define the interfaces and write the detailed definition in a JSON file.

Interfaces:

export interface settingDef{
  name:string;
  type:"boolean"|"number"|"string";
  templateStructureType?:"FormatSpecification"|"ImageParameter"|"RegionDefinition";
  description?:string;
  max?:number;
  min?:number;
  default?:number;
}

definition.json:

{
  "settings":
  [
    {
      "name":"ExpectedBarcodesCount",
      "templateStructureType":"ImageParameter",
      "type":"number",
      "max": 999,
      "min": 0,
      "default": 0
    },
    {
      "name":"DeblurLevel",
      "templateStructureType":"ImageParameter",
      "type":"number",
      "max": 9,
      "min": 0,
      "default": 9
    },
    {
      "name":"Timeout",
      "templateStructureType":"ImageParameter",
      "type":"number",
      "max": 99999,
      "min": 0,
      "default": 10000
    },
    {
      "name":"ScaleDownThreshold",
      "templateStructureType":"ImageParameter",
      "type":"number",
      "max": 99999,
      "min": 8,
      "default": 2300
    },
    {
      "name":"MinResultConfidence",
      "templateStructureType":"FormatSpecification",
      "type":"number",
      "max": 100,
      "min": 0,
      "default": 30
    }
  ]
}

Render the UI for Adjusting the General Parameters

Render each setting.

renderOneSetting(def:settingDef){
  if (def.type === "number") {
    return (
      <div>
        <label>
          {def.name}
        </label>
        <input onChange={(event)=>this.handleInput(event,def)} type="range" min={def.min ?? 0} max={def.max ?? 1000} step="1" value={this.items[def.name] ?? def.default}/>
        <input onChange={(event)=>this.handleInput(event,def)} type="number" min={def.min ?? 0} max={def.max ?? 1000} value={this.items[def.name] ?? def.default}></input>
      </div>
    );
  }
  return "";
}

render() {
  return (
    <Host>
      <div class="settings">
        {this.settingDefinitions.map(def => (
          this.renderOneSetting(def)
        ))}
      </div>
      <slot></slot>
    </Host>
  );
}

Methods for updating the settings:

@State() rerender: boolean = false;
items:{} = {}; //store modifed items
settings:any;
handleInput(event:any,def:settingDef) {
  let target = event.target;
  let value = parseInt(target.value);
  if (def.type === "number") {
    this.items[def.name] = value;
  }
  this.dataModified();
}

dataModified(){
  this.rerender = !this.rerender;
}

Load and Output the Settings

We can update the controls by loading a setting object like the following and output the settings to it as well:

{
  "ImageParameter": {
    "ExpectedBarcodesCount": 999,
    "FormatSpecificationNameArray": [
      "default"
    ],
    "Name": "Settings"
  },
  "Version": "3.0",
  "FormatSpecification": {
    "Name": "default",
    "MinResultConfidence": 30
  }
}

Define a method to load the settings:

@Method()
async loadSettings(settings:any){
  this.settings = settings;
  for (let index = 0; index < this.settingDefinitions.length; index++) {
    const settingDef = this.settingDefinitions[index];
    if (settings[settingDef.templateStructureType]) {
      if (settings[settingDef.templateStructureType][settingDef.name]) {
        this.items[settingDef.name] = settings[settingDef.templateStructureType][settingDef.name];
      }
    }
  }
  this.dataModified();
}

Define a method to output the settings:

@Method()
async outputSettings(){
  let output = {
    FormatSpecification:{
      Name:"default"
    },
    ImageParameter:{
      FormatSpecificationNameArray:["default"]
    }
  };
  if (this.settings.FormatSpecification && this.settings.FormatSpecification.Name) {
     output.FormatSpecification.Name = this.settings.FormatSpecification.Name;
     delete output.ImageParameter.FormatSpecificationNameArray;
  }
  
  for (let index = 0; index < this.settingDefinitions.length; index++) {
    const settingDef = this.settingDefinitions[index];
    let item = this.items[settingDef.name];
    if (item) {
      output[settingDef.templateStructureType][settingDef.name] = item;
    }
  }
  if (!this.settings.FormatSpecification) {
    this.settings.FormatSpecification = output.FormatSpecification;
  }else{
    for (let key in output.FormatSpecification) {
      this.settings.FormatSpecification[key] = output.FormatSpecification[key];
    }
  }
  for (let key in output.ImageParameter) {
    this.settings.ImageParameter[key] = output.ImageParameter[key];
  }
  return this.settings;
}

Use the Component

  1. Add the component in HTML:

    <general-settings></general-settings>
    
  2. Load the settings from a template:

    let template = "{\"ImageParameter\":{\"ExpectedBarcodesCount\":999},\"Version\":\"3.0\"}";
    let settings = JSON.parse(template);
    let generalSettings = document.querySelector("general-settings");
    generalSettings.loadSettings(settings);
    
  3. We can get the updated settings with the outputSettings method:

    let generalSettings = document.querySelector("general-settings");
    let settings = await generalSettings.outputSettings();
    

All right, we’ve completed writing the components.

We can use the components to modify an existing JSON template and use it to update the runtime settings of Dynamsoft Barcode Reader.

You can try out the online demo which can decode static images or do a live scan.

Source Code

Check out the source code to have a try. You can know about how to integrate the components in your projects.

https://github.com/tony-xlh/parameters-tuner