import React from 'react'; import PropTypes from 'prop-types';
import style from 'PVWStyle/ReactWidgets/LookupTableWidget.mcss';
import ColorPicker from '../ColorPickerWidget'; import NumberInputWidget from '../NumberInputWidget';
const STYLE = { range: { none: { display: 'flex', }, edit: { display: 'flex', }, preset: { display: 'none', }, }, editContent: { none: { display: 'none', }, edit: { display: 'flex', }, preset: { display: 'none', }, }, presets: { none: { display: 'none', }, edit: { display: 'none', }, preset: { display: 'flex', }, }, };
export default class LookupTableWidget extends React.Component { constructor(props) { super(props); this.state = { mode: 'none', activePreset: props.lookupTable.getPresets()[0], currentControlPointIndex: 0, internal_lut: false, originalRange: props.originalRange, };
this.setPreset = this.setPreset.bind(this); this.updateScalarRange = this.updateScalarRange.bind(this); this.addControlPoint = this.addControlPoint.bind(this); this.deleteControlPoint = this.deleteControlPoint.bind(this); this.nextControlPoint = this.nextControlPoint.bind(this); this.previousControlPoint = this.previousControlPoint.bind(this); this.updateScalar = this.updateScalar.bind(this); this.updateRGB = this.updateRGB.bind(this); this.toggleEditMode = this.toggleEditMode.bind(this); this.togglePresetMode = this.togglePresetMode.bind(this); this.attachListener = this.attachListener.bind(this); this.removeListener = this.removeListener.bind(this); this.updateOriginalRange = this.updateOriginalRange.bind(this); this.resetRange = this.resetRange.bind(this); this.changePreset = this.changePreset.bind(this); this.nextPreset = this.nextPreset.bind(this); this.previousPreset = this.previousPreset.bind(this); this.deltaPreset = this.deltaPreset.bind(this); }
componentWillMount() { this.attachListener(this.props.lookupTable); }
componentDidMount() { const canvas = this.canvas; this.props.lookupTable.drawToCanvas(canvas); }
componentWillReceiveProps(nextProps) { if (nextProps.lookupTable !== this.props.lookupTable) { this.removeListener(); this.attachListener(nextProps.lookupTable); } if ( this.props.originalRange[0] !== nextProps.originalRange[0] || this.props.originalRange[1] !== nextProps.originalRange[1] ) { this.setState({ originalRange: nextProps.originalRange }); } }
componentDidUpdate(prevProps, prevState) { if (!this.state.internal_lut) { const canvas = this.canvas; this.props.lookupTable.drawToCanvas(canvas);
if (this.state.mode === 'edit') { const ctx = canvas.getContext('2d'); const x = Math.floor( this.props.lookupTable.getControlPoint( this.state.currentControlPointIndex ).x * this.props.lookupTable.colorTableSize ); const imageData = ctx.getImageData( 0, 0, this.props.lookupTable.colorTableSize, 1 );
const color = imageData.data[x * 4] + imageData.data[x * 4 + 1] + imageData.data[x * 4 + 2] > (3 * 255) / 2 ? 0 : 255; imageData.data[x * 4 + 0] = this.props.inverse ? (imageData.data[x * 4 + 0] + 128) % 256 : color; imageData.data[x * 4 + 1] = this.props.inverse ? (imageData.data[x * 4 + 1] + 128) % 256 : color; imageData.data[x * 4 + 2] = this.props.inverse ? (imageData.data[x * 4 + 2] + 128) % 256 : color;
ctx.putImageData(imageData, 0, 0); } } }
componentWillUnmount() { this.removeListener(); }
setPreset(event) { this.props.lookupTable.setPreset(event.target.dataset.name); this.togglePresetMode(); }
updateScalarRange() { const originalRange = this.props.lookupTable.getScalarRange(); const minValue = this.min.getValue() || originalRange[0]; const maxValue = this.max.getValue() || originalRange[1];
this.props.lookupTable.setScalarRange( minValue, minValue === maxValue ? maxValue + 1 : maxValue ); this.forceUpdate(); }
addControlPoint() { const newIdx = this.props.lookupTable.addControlPoint({ x: 0.5, r: 0, g: 0, b: 0, }); this.setState({ currentControlPointIndex: newIdx }); }
deleteControlPoint() { if ( this.props.lookupTable.removeControlPoint( this.state.currentControlPointIndex ) ) { this.forceUpdate(); } }
nextControlPoint() { const newIdx = this.state.currentControlPointIndex + 1;
if (newIdx < this.props.lookupTable.getNumberOfControlPoints()) { this.setState({ currentControlPointIndex: newIdx }); } }
previousControlPoint() { const newIdx = this.state.currentControlPointIndex - 1;
if (newIdx > -1) { this.setState({ currentControlPointIndex: newIdx }); } }
updateScalar(newVal) { const scalarRange = this.props.lookupTable.getScalarRange(); const xValue = (newVal - scalarRange[0]) / (scalarRange[1] - scalarRange[0]); const controlPoint = this.props.lookupTable.getControlPoint( this.state.currentControlPointIndex );
const newIdx = this.props.lookupTable.updateControlPoint( this.state.currentControlPointIndex, { x: xValue, r: controlPoint.r, g: controlPoint.g, b: controlPoint.b, } ); this.setState({ currentControlPointIndex: newIdx }); this.forceUpdate(); }
updateRGB(rgb) { const controlPoint = this.props.lookupTable.getControlPoint( this.state.currentControlPointIndex );
const newIdx = this.props.lookupTable.updateControlPoint( this.state.currentControlPointIndex, { x: controlPoint.x, r: rgb[0] / 255, g: rgb[1] / 255, b: rgb[2] / 255, } ); this.setState({ currentControlPointIndex: newIdx }); }
toggleEditMode() { if (this.state.mode === 'none' || this.state.mode !== 'edit') { this.setState({ mode: 'edit', internal_lut: false }); } else { this.setState({ mode: 'none', internal_lut: false }); } }
togglePresetMode() { if (this.state.mode === 'none' || this.state.mode !== 'preset') { this.deltaPreset(0); this.setState({ mode: 'preset', internal_lut: true }); } else { this.setState({ mode: 'none', internal_lut: false }); } }
attachListener(lut) { this.subscription = lut.onChange((data, envelope) => { this.forceUpdate(); }); }
removeListener() { if (this.subscription) { this.subscription.unsubscribe(); this.subscription = null; } }
updateOriginalRange(min, max) { console.log( `Someone asked LookupTableWidget to update original range to [${min}, ${max}]` ); this.setState({ originalRange: [min, max] }); }
resetRange() { const range = this.state.originalRange; const currentRange = this.props.lookupTable.getScalarRange(); console.log( `LookupTableWidget current range: [${currentRange[0]}, ${currentRange[1]}], new range: [${range[0]}, ${range[1]}]` ); this.props.lookupTable.setScalarRange(range[0], range[1]); }
changePreset(event) { const delta = event.detail || event.deltaY || event.deltaX; event.preventDefault(); this.deltaPreset(delta); }
nextPreset() { this.deltaPreset(1); }
previousPreset() { this.deltaPreset(-1); }
deltaPreset(delta) { const presets = this.props.lookupTable.getPresets(); let currentIdx = presets.indexOf(this.state.activePreset); let newPreset = null;
currentIdx += delta === 0 ? 0 : delta < 0 ? -1 : 1; if (currentIdx < 0 || currentIdx === presets.length) { return; }
newPreset = presets[currentIdx]; if (this.props.lookupTableManager) { let lut = this.props.lookupTableManager.getLookupTable('__internal'); if (!lut) { lut = this.props.lookupTableManager.addLookupTable( '__internal', [0, 1], newPreset ); } else { lut.setPreset(newPreset); } lut.drawToCanvas(this.canvas); } this.setState({ activePreset: newPreset }); }
render() { const scalarRange = this.props.lookupTable.getScalarRange(); const controlPoint = this.props.lookupTable.getControlPoint( this.state.currentControlPointIndex ); const controlPointValue = controlPoint.x * (scalarRange[1] - scalarRange[0]) + scalarRange[0]; const color = [ Math.floor(255 * controlPoint.r), Math.floor(255 * controlPoint.g), Math.floor(255 * controlPoint.b), ];
return ( <div className={style.container}> <div className={style.line}> <i className={style.editButton} onClick={this.toggleEditMode} /> <canvas ref={(c) => { this.canvas = c; }} className={style.canvas} width={ this.props.lookupTable.colorTableSize * this.props.lookupTable.scale } height="1" /> <i className={style.presetButton} onClick={this.togglePresetMode} /> </div> <div className={style.range} style={STYLE.range[this.state.mode]}> <NumberInputWidget ref={(c) => { this.min = c; }} className={style.input} value={this.props.lookupTable.getScalarRange()[0]} onChange={this.updateScalarRange} /> <i onClick={this.resetRange} className={style.resetRangeButton} /> <NumberInputWidget ref={(c) => { this.max = c; }} className={style.inputRight} value={this.props.lookupTable.getScalarRange()[1]} onChange={this.updateScalarRange} /> </div> <div className={style.editContent} style={STYLE.editContent[this.state.mode]} > <div className={style.line}> <i onClick={this.previousControlPoint} className={style.previousButton} /> <div className={style.label}> {this.state.currentControlPointIndex + 1}/{' '} {this.props.lookupTable.getNumberOfControlPoints()} </div> <i onClick={this.nextControlPoint} className={style.nextButton} /> <i onClick={this.addControlPoint} className={style.addButton} /> <NumberInputWidget className={style.inputRight} value={controlPointValue} onChange={this.updateScalar} /> <i onClick={this.deleteControlPoint} className={style.deleteButton} /> </div> <ColorPicker color={color} onChange={this.updateRGB} /> </div> <div className={style.presets} style={STYLE.presets[this.state.mode]}> <i onClick={this.previousPreset} className={ this.state.activePreset === this.props.lookupTable.getPresets()[0] ? style.disablePreviousButton : style.previousButton } /> {this.props.lookupTable.getPresets().map((preset) => ( <div onClick={this.setPreset} onScroll={this.changePreset} onWheel={this.changePreset} className={ this.state.activePreset === preset ? style.preset : style.hiddenPreset } data-name={preset} key={preset} > {preset} </div> ))} <i onClick={this.nextPreset} className={ this.state.activePreset === this.props.lookupTable.getPresets()[ this.props.lookupTable.getPresets().length - 1 ] ? style.disableNextButton : style.nextButton } /> </div> </div> ); } }
LookupTableWidget.propTypes = { inverse: PropTypes.bool, lookupTable: PropTypes.object.isRequired, lookupTableManager: PropTypes.object, originalRange: PropTypes.array, };
LookupTableWidget.defaultProps = { inverse: false, lookupTableManager: undefined, originalRange: undefined, };
|