반응형

WebGL 결과물은 보통 PNG 이미지와 비교를 하는 방식으로 테스팅을 진행한다.

canvas의 context가 2D 인 경우, context의 imageData를 추출하고 pixelmatch의 도움을 받아 다음과 같이 진행하면 된다(imageData 자체를 비교해도 무관하다).

const width = targetCanvas.width;
const height = targetCanvas.height;

const refCanvas = document.createElement("canvas");
refCanvas.width = width;
refCanvas.height = height;

const refContext = refCanvas.getContext("2d");
refContext.drawImage(image, 0, 0);

const mismatch = pixelmatch(
  context.getImageData(0, 0, width, height).data,
  refContext.getImageData(0, 0, width, height).data,
  null, width, height);
// mismatch === 일치하지 않는 픽셀 수

하지만 canvas의 context가 webgl 인 경우, imageData를 추출할 수 없다. 이 때 canvas의 데이터를 얻을 수 있는 유일한 방법은 toDataURL() 을 호출하는 것인데, drawImage 의 첫 번째 파라미터로 toDataURL() 의 결과를 입력할 경우 타입 에러가 발생한다.

따라서 2D의 경우와 같은 방식으로 진행할 수 없다.

 

이를 해결 하기 위해선 webgl context를 갖는 canvas를 2d context를 갖는 canvas로 먼저 복사한 후, 위 과정을 진행하면 된다.

const width = targetCanvas.width;
const height = targetCanvas.height;

const copiedTargetCanvas = document.createElement("canvas");
copiedTargetCanvas.width = width;
copiedTargetCanvas.height = height;

const copiedTargetContext = copiedTargetCanvas.getContext("2d");
copiedTargetContext.drawImage(targetContext.canvas, 0, 0, width, height);

const targetData = copiedTargetContext.getImageData(0, 0, width, height).data;

const refCanvas = document.createElement("canvas");
refCanvas.width = width;
refCanvas.height = height;

const refContext = refCanvas.getContext("2d");
refContext.drawImage(image, 0, 0);

const refData = refContext.getImageData(0, 0, width, height).data;

// targetData 와 refData 가 일치하는지 비교
// 일치하면 동일한 이미지, 그렇지 않으면 다른 이미지.
반응형
반응형

JavaScript 에서도 getter, setter 를 지원한다.

사용하는 방법에 대한 설명은 어디에서나 찾을 수 있었는데, 언제 그리고 왜 사용해야하는지는 찾을 수 없어서 직접 케이스를 만들어 보았다.

 

Use case: 자료구조형 클래스 선언

// 기본 클래스 선언
class Bounds {
  constructor(southWest, northEast) {
    this.south = southWest.south;
    this.west = southWest.west;
    this.north = northEast.north;
    this.east = northEast.east;
  }
}

 

 

southWest, northEast 값을 한 번에 얻고 싶다는 요구 사항이 들어온다면?

class Bounds {
  constructor(southWest, northEast) {
    this.south = southWest.south;
    this.west = southWest.west;
    this.north = northEast.north;
    this.east = northEast.east;
    
    // southWest, northEast 값을 반환하는 메소드를 선언한다
    this.getSouthWest = () => {
      return {
        south: this.south,
        west: this.west,
      };
    };
    this.getNorthEast = () => {
      return {
        north: this.north,
        east: this.east,
      };
    }; 
  }
}
메소드를 통해 원하는 값을 반환하도록 할 수 있다. 하지만 southWest, northEast 값을 수정할 수는 없다. 따라서 다음과 같이 개선해볼 수 있다.
class Bounds {
  constructor(southWest, northEast) {
    this.south = southWest.south;
    this.west = southWest.west;
    this.north = northEast.north;
    this.east = northEast.east;
    this.getSouthWest = () => {
      return {
        south: this.south,
        west: this.west,
      };
    };
    this.getNorthEast = () => {
      return {
        north: this.north,
        east: this.east,
      };
    };

    // southWest/northEast 를 설정하는 메소드를 선언한다
    this.setSouthWest = (southWest) => {
      this.south = southWest.south;
      this.west = southWest.west;
    };
    this.setNorthEast = (northEast) => {
      this.north = northEast.north;
      this.east = northEast.east;
    };
  }
}

// 사용 예:
const bounds = new Bounds({
  south: 1,
  west: 2,
}, {
  north: 3,
  east: 4,
});
bounds.south // 1
bounds.west // 2
bounds.north //3
bounds.east // 4
bounds.getSouthWest() // {south: 1, west: 2}
bounds.getNorthEast() // {north: 3, east: 4}
bounds.setSouthWest({south: 10, west: 20})
bounds.setNorthEast({north: 30, east: 40})

위 코드를 통해 south/west/north/east 및 southWest, northEast를 설정하고 반환하도록 처리할 수 있다.

하지만, 다시 코드를 살펴보면 south/west/north/east 의 경우 변수에 직접 접근하여 값을 설정하고 반환할 수 있는 반면 southWest, northEast 의 경우 메소드를 통해 값을 설정하고 반환해야 한다.

자료구조 클래스의 값에 접근하는 방식에 일관성이 깨진 것이다. 일관성을 유지하려면 getter, setter 를 활용하면 된다.

class Bounds {
  constructor(southWest, northEast) {
    this.south = southWest.south;
    this.west = southWest.west;
    this.north = northEast.north;
    this.east = northEast.east;
  }
  get southWest() {
    return {
      south: this.south,
      west: this.west,
    };
  };
  get northEast() {
    return {
      north: this.north,
      east: this.east,
    };
  };
  set southWest(southWest) {
    this.south = southWest.south;
    this.west = southWest.west;
  };
  set northEast(northEast) {
    this.north = northEast.north;
    this.east = northEast.east;
  };
}

// 사용 예:
/*
const bounds = new Bounds({
  south: 1,
  west: 2,
}, {
  north: 3,
  east: 4,
});

bounds.south // 1
bounds.west // 2
bounds.north //3
bounds.east // 4
bounds.southWest // {south: 1, west: 2}
bounds.northEast // {north: 3, east: 4}
bounds.southWest = {south: 10, west: 20}
bounds.northEast = {north: 30, east: 40}
*/
 

 

자, 이제 southWest, northEast 도 south/west/north/east 와 같은 방식으로 변수처럼 값을 설정하고 반환할 수 있다.

반응형
반응형

TIF: Today I Failed

실패담입니다. 아직 해결하지 못한 문제입니다.




- 설명

내부 로직에서만 공유되어야하는 값의 경우, private 변수 선언을 통해 외부에서 접근할 수 없도록 해야합니다. 하지만, JavaScript 에서는 private 변수를 지원하지 않습니다. 따라서, 기괴한? 방법을 사용해 이를 우회해 구현해야 합니다.


- 시도

  1. _(언더바) 로 private 변수 구분 후, 난독화 처리(옛날 방식).
    효과: 난독화되면 해당 값이 무엇을 의미하는지 알 수 없으므로, 의도적인 사용을 방지할 수 있다.
    단점: 여전히 접근은 가능하다. 난독화 라이브러리와 babel 과의 호환문제로, ES6+ 에서는 사실상 사용이 불가하다.
           (참고: https://github.com/terser-js/terser/issues/237)

    class MyClass {
      constructor() {
        this._myPrivate = "private";
      }

      setValue(value) {
        this._myPrivate = value;
      }
    }

  2. scope 활용 & 메소드를 constructor 내부에 선언.
    효과: class 내부에서만 사용이 가능하다.
    단점: private 변수에 접근이 필요한 모든 메소드를 constructor 내부에 위치시켜야 하므로, constructor 가 길어진다.
           해당 클래스를 상속할 경우, 클래스의 메소드에 접근이 불가하다.

    class MyClass {
      constructor() {
        let myPrivate = "private";
       
        this.setValue = (value) => {
          myPrivate = value;
        };
      }
    }

  3. 내부 메소드 접근을 위한 자체 난독화 메소드 구현
    효과: class 를 상속하더라도 private 변수에 접근이 가능하다.
    단점: 더럽다. 코드를 오픈하면, 어떻게 private 변수에 접근하는지 추측이 어느정도 가능하다.

    class MyClass {
      constructor() {
        let myPrivate = "private";

        this.__private__ = {
          set: (key, value) => {
            if (key === "_") {
              myPrivate = value
            }
          },
        };
      }

      setValue(value) {
        this.__private__.set("_", value);
      }
    }

   

- 원하는 결과


class MyClass {
  constructor() {
    let myPrivate = "private";
  }

  setValue(value) {
    myPrivate = value;
  }
}


반응형

+ Recent posts