src_image_maskSegmentHelper.js

import {logger} from '../utils/logger';

// doc imports
/* eslint-disable no-unused-vars */
import {Image} from './image';
/* eslint-enable no-unused-vars */

/**
 * Mask segment helper.
 */
export class MaskSegmentHelper {

  /**
   * The associated mask.
   *
   * @type {Image}
   */
  #mask;

  /**
   * The segments: array of segment description.
   *
   * @type {Array}
   */
  #segments;

  /**
   * List of ids of hidden segments.
   *
   * @type {Array}
   */
  #hiddenSegments = [];

  /**
   * @param {Image} mask The associated mask image.
   */
  constructor(mask) {
    this.#mask = mask;
    this.#segments = mask.getMeta().custom.segments;
  }

  /**
   * Check if a segment is part of the inner segment list.
   *
   * @param {number} segmentNumber The segment number.
   * @returns {boolean} True if the segment is included.
   */
  hasSegment(segmentNumber) {
    return typeof this.getSegment(segmentNumber) !== 'undefined';
  }

  /**
   * Check if a segment is present in a mask image.
   *
   * @param {Array} numbers Array of segment numbers.
   * @returns {Array} Array of boolean set to true
   *   if the segment is present in the mask.
   */
  maskHasSegments(numbers) {
    // create values using displayValue
    const values = [];
    const unknowns = [];
    for (let i = 0; i < numbers.length; ++i) {
      const segment = this.getSegment(numbers[i]);
      if (typeof segment !== 'undefined') {
        values.push(segment.displayValue);
      } else {
        logger.warn('Unknown segment in maskHasSegments: ' + numbers[i]);
        unknowns.push(i);
      }
    }
    const res = this.#mask.hasValues(values);
    // insert unknowns as false in result
    for (let j = 0; j < unknowns.length; ++j) {
      res.splice(unknowns[j], 0, false);
    }
    return res;
  }

  /**
   * Get a segment from the inner segment list.
   *
   * @param {number} segmentNumber The segment number.
   * @returns {object} The segment.
   */
  getSegment(segmentNumber) {
    return this.#segments.find(function (item) {
      return item.number === segmentNumber;
    });
  }

  /**
   * Get the inner segment list.
   *
   * @returns {Array} The list of segments.
   */
  getSegments() {
    return this.#segments;
  }

  /**
   * Set the inner segment list.
   *
   * @param {Array} list The segment list.
   */
  setSegments(list) {
    this.#segments = list;
  }

  /**
   * Set the hidden segment list.
   * TODO: not sure if needed...
   *
   * @param {Array} list The list of hidden segment numbers.
   */
  setHiddenSegments(list) {
    this.#hiddenSegments = list;
  }

  /**
   * Get the index of a segment in the hidden list.
   *
   * @param {number} segmentNumber The segment number.
   * @returns {number|undefined} The index in the array.
   */
  #getHiddenIndex(segmentNumber) {
    return this.#hiddenSegments.findIndex(function (item) {
      return item === segmentNumber;
    });
  }

  /**
   * Check if a segment is in the hidden list.
   *
   * @param {number} segmentNumber The segment number.
   * @returns {boolean} True if the segment is in the list.
   */
  isHidden(segmentNumber) {
    return this.#getHiddenIndex(segmentNumber) !== -1;
  }

  /**
   * Add a segment to the hidden list.
   *
   * @param {number} segmentNumber The segment number.
   */
  addToHidden(segmentNumber) {
    if (!this.isHidden(segmentNumber)) {
      this.#hiddenSegments.push(segmentNumber);
    } else {
      logger.warn(
        'Segment is allready in the hidden list: ' + segmentNumber);
    }
  }

  /**
   * Remove a segment from the hidden list.
   *
   * @param {number} segmentNumber The segment number.
   */
  removeFromHidden(segmentNumber) {
    const index = this.#getHiddenIndex(segmentNumber);
    if (index !== -1) {
      this.#hiddenSegments.splice(index, 1);
    } else {
      logger.warn('Segment is not in the hidden list: ' + segmentNumber);
    }
  }

  /**
   * @callback alphaFn
   * @param {object} value The pixel value.
   * @param {object} index The values' index.
   * @returns {number} The value to display.
   */

  /**
   * Get the alpha function to apply hidden colors.
   *
   * @returns {alphaFn} The corresponding alpha function.
   */
  getAlphaFunc() {
    // get colours
    const hiddenColours = [{r: 0, g: 0, b: 0}];
    for (let i = 0; i < this.#hiddenSegments.length; ++i) {
      const segment = this.getSegment(this.#hiddenSegments[i]);
      if (typeof segment !== 'undefined') {
        hiddenColours.push(segment.displayValue);
      }
    }

    // create alpha function
    return function (value/*, index*/) {
      for (let i = 0; i < hiddenColours.length; ++i) {
        if (value[0] === hiddenColours[i].r &&
          value[1] === hiddenColours[i].g &&
          value[2] === hiddenColours[i].b) {
          return 0;
        }
      }
      // default
      return 255;
    };
  }

  /**
   * @callback eventFn
   * @param {object} event The event.
   */

  /**
   * Delete a segment.
   *
   * @param {number} segmentNumber The segment number.
   * @param {eventFn} cmdCallback The command event callback.
   * @param {Function} exeCallback The post execution callback.
   */
  deleteSegment(segmentNumber, cmdCallback, exeCallback) {
    const delcmd = new DeleteSegmentCommand(
      this.#mask, this.getSegment(segmentNumber));
    delcmd.onExecute = cmdCallback;
    delcmd.onUndo = cmdCallback;
    if (delcmd.isValid()) {
      delcmd.execute();
      // callback
      exeCallback(delcmd);
      // possibly hidden
      if (this.isHidden(segmentNumber)) {
        this.removeFromHidden(segmentNumber);
      }
    }
  }

} // class MaskSegmentHelper

/**
 * Delete segment command.
 */
export class DeleteSegmentCommand {

  /**
   * The associated mask.
   *
   * @type {Image}
   */
  #mask;

  /**
   * The segment to remove.
   *
   * @type {object}
   */
  #segment;

  /**
   * Flag to send creation events.
   *
   * @type {boolean}
   */
  #isSilent;

  /**
   * List of offsets.
   *
   * @type {Array}
   */
  #offsets;

  /**
   * @param {Image} mask The mask image.
   * @param {object} segment The segment to remove.
   * @param {boolean} [silent] Whether to send a creation event or not.
   */
  constructor(mask, segment, silent) {
    this.#mask = mask;
    this.#segment = segment;

    this.#isSilent = (typeof silent === 'undefined') ? false : silent;
    // list of offsets with the colour to delete
    this.#offsets = mask.getOffsets(segment.displayValue);
  }

  /**
   * Get the command name.
   *
   * @returns {string} The command name.
   */
  getName() {
    return 'Delete-segment';
  }

  /**
   * Check if a command is valid and can be executed.
   *
   * @returns {boolean} True if the command is valid.
   */
  isValid() {
    return this.#offsets.length !== 0;
  }

  /**
   * Execute the command.
   *
   * @fires DeleteSegmentCommand#masksegmentdelete
   */
  execute() {
    // remove
    this.#mask.setAtOffsets(this.#offsets, {r: 0, g: 0, b: 0});

    // callback
    if (!this.#isSilent) {
      /**
       * Segment delete event.
       *
       * @event DeleteSegmentCommand#masksegmentdelete
       * @type {object}
       * @property {number} segmentnumber The segment number.
       */
      this.onExecute({
        type: 'masksegmentdelete',
        segmentnumber: this.#segment.number
      });
    }
  }

  /**
   * Undo the command.
   *
   * @fires DeleteSegmentCommand#masksegmentredraw
   */
  undo() {
    // re-draw
    this.#mask.setAtOffsets(this.#offsets, this.#segment.displayValue);

    // callback
    /**
     * Segment redraw event.
     *
     * @event DeleteSegmentCommand#masksegmentredraw
     * @type {object}
     * @property {number} segmentnumber The segment number.
     */
    this.onUndo({
      type: 'masksegmentredraw',
      segmentnumber: this.#segment.number
    });
  }

  /**
   * Handle an execute event.
   *
   * @param {object} _event The execute event with type and id.
   */
  onExecute(_event) {
    // default does nothing.
  }

  /**
   * Handle an undo event.
   *
   * @param {object} _event The undo event with type and id.
   */
  onUndo(_event) {
    // default does nothing.
  }

} // DeleteSegmentCommand class