Skip to content

Commit

Permalink
Added a make-picture component
Browse files Browse the repository at this point in the history
This allows to create pictures of any size from the current view, thus not limited by the screen size as is the case with the screenshot mode.
  • Loading branch information
Sebastien Ponce committed Jan 6, 2023
1 parent 8b924db commit f2027bb
Show file tree
Hide file tree
Showing 11 changed files with 280 additions and 0 deletions.
19 changes: 19 additions & 0 deletions packages/phoenix-event-display/src/event-display.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,25 @@ export class EventDisplay {
this.enableKeyboardControls();
}

/**
* Takes a screen shot of the current view
* @param width the width of the picture to be created
* @param height the height of the picture to be created
* @param fitting the type of fitting to use in case width and height
* ratio do not match the current screen ratio. Posible values are
* - Crop : current view is cropped on both side or up and done to fit ratio
* thus it is not streched, but some parts are lost
* - Strech : current view is streched to given format
* this is the default and used also for any other value given to fitting
*/
public makeScreenShot(
width: number,
height: number,
fitting: string = 'Strech'
) {
this.graphicsLibrary.makeScreenShot(width, height, fitting);
}

/**
* Initialize XR.
* @param xrSessionType Type of the XR session. Either AR or VR.
Expand Down
114 changes: 114 additions & 0 deletions packages/phoenix-event-display/src/managers/three-manager/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ import {
MeshBasicMaterial,
Euler,
PerspectiveCamera,
Vector2,
} from 'three';
import html2canvas from 'html2canvas';
import { Configuration } from '../../lib/types/configuration';
import { ControlsManager } from './controls-manager';
import { RendererManager } from './renderer-manager';
Expand Down Expand Up @@ -755,6 +757,118 @@ export class ThreeManager {
this.animationsManager.animateClippingWithCollision(tweenDuration, onEnd);
}

saveBlob = (function () {
const a = document.createElement('a');
document.body.appendChild(a);
a.style.display = 'none';
return function saveData(blob, fileName) {
const url = window.URL.createObjectURL(blob);
a.href = url;
a.download = fileName;
a.click();
};
})();

/**
* Takes a screen shot of the current view
* @param width the width of the picture to be created
* @param height the height of the picture to be created
* @param fitting the type of fitting to use in case width and height
* ratio do not match the current screen ratio. Posible values are
* - Crop : current view is cropped on both side or up and done to fit ratio
* thus it is not streched, but some parts are lost
* - Strech : current view is streched to given format
* this is the default and used also for any other value given to fitting
*/
public makeScreenShot(
width: number,
height: number,
fitting: string = 'Strech'
) {
// compute actual size of screen shot, based on current view and reuested size
const mainRenderer = this.rendererManager.getMainRenderer();
var originalSize = new Vector2();
mainRenderer.getSize(originalSize);
var scaledHeight = height;
var scaledWidth = width;
if (fitting == 'Crop') {
// Massage width and height so that we keep the screen ratio
// and thus the image from the screen is not streched
if (originalSize.width * height < originalSize.height * width) {
scaledHeight = (originalSize.height * width) / originalSize.width;
} else {
scaledWidth = (originalSize.width * height) / originalSize.height;
}
}
const heightShift = (scaledHeight - height) / 2;
const widthShift = (scaledWidth - width) / 2;

// get background color to be used
var bkgColor = getComputedStyle(document.body).getPropertyValue(
'--phoenix-background-color'
);

// grab output canvas on which we will draw, and set size
var outputCanvas = document.getElementById(
'screenshotCanvas'
) as HTMLCanvasElement;
outputCanvas.width = width;
outputCanvas.height = height;
var ctx = outputCanvas.getContext('2d');
ctx.fillStyle = bkgColor;
ctx.fillRect(0, 0, width, height);
// draw main image on our output canvas, with right size
mainRenderer.setSize(scaledWidth, scaledHeight, false);
this.render();
ctx.drawImage(
mainRenderer.domElement,
widthShift,
heightShift,
width,
height,
0,
0,
width,
height
);
mainRenderer.setSize(originalSize.width, originalSize.height, false);
this.render();

// Get info panel
const infoPanel = document.getElementById('experimentInfo');
if (infoPanel != null) {
// Compute size of info panel on final picture
const infoHeight =
(infoPanel.clientHeight * scaledHeight) / originalSize.height;
const infoWidth =
(infoPanel.clientWidth * scaledWidth) / originalSize.width;

// Add info panel to output. This is HTML, so first convert it to canvas,
// and then draw to our output canvas
html2canvas(infoPanel, { backgroundColor: bkgColor }).then((canvas) => {
canvas.toBlob((blob) => {
ctx.drawImage(
canvas,
infoHeight / 6,
infoHeight / 6,
infoWidth,
infoHeight
);
// Finally save to png file
outputCanvas.toBlob((blob) => {
const a = document.createElement('a');
document.body.appendChild(a);
a.style.display = 'none';
const url = window.URL.createObjectURL(blob);
a.href = url;
a.download = `screencapture.png`;
a.click();
});
});
});
}
}

/**
* Initialize the VR session.
* @param xrSessionType Type of the XR session. Either AR or VR.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<app-vr-toggle></app-vr-toggle>
<app-ar-toggle></app-ar-toggle>
<app-ss-mode></app-ss-mode>
<app-make-picture></app-make-picture>
<app-io-options
[eventDataImportOptions]="eventDataImportOptions"
></app-io-options>
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { DragDropModule } from '@angular/cdk/drag-drop';
import { OverlayModule } from '@angular/cdk/overlay';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatDialogModule } from '@angular/material/dialog';
import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu';
import { MatRadioModule } from '@angular/material/radio';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatSliderModule } from '@angular/material/slider';
import { MatTooltipModule } from '@angular/material/tooltip';
Expand Down Expand Up @@ -50,6 +52,7 @@ import {
VrToggleComponent,
ArToggleComponent,
SSModeComponent,
MakePictureComponent,
PerformanceToggleComponent,
ShareLinkComponent,
ShareLinkDialogComponent,
Expand Down Expand Up @@ -102,6 +105,7 @@ const PHOENIX_COMPONENTS: Type<any>[] = [
VrToggleComponent,
ArToggleComponent,
SSModeComponent,
MakePictureComponent,
PerformanceToggleComponent,
LoaderComponent,
ShareLinkComponent,
Expand All @@ -125,7 +129,9 @@ const PHOENIX_COMPONENTS: Type<any>[] = [
MatButtonModule,
MatTooltipModule,
OverlayModule,
FormsModule,
MatMenuModule,
MatRadioModule,
MatSliderModule,
MatSlideToggleModule,
MatCheckboxModule,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export * from './vr-toggle/vr-toggle.component';
export * from './ar-toggle/ar-toggle.component';
export * from './zoom-controls/zoom-controls.component';
export * from './ss-mode/ss-mode.component';
export * from './make-picture/make-picture.component';
export * from './performance-toggle/performance-toggle.component';
export * from './ui-menu.component';
export * from './share-link/share-link.component';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<mat-menu class="mat-menu" #menu>
<button mat-menu-item (click)="$event.stopPropagation()" class="size-input">
<label>width</label>
<input
#width
mapInput
type="number"
[value]="3840"
(change)="setWidth(width.value)"
(click)="$event.stopPropagation()"
/>
</button>
<button mat-menu-item (click)="$event.stopPropagation()" class="size-input">
<label>height</label>
<input
#height
mapInput
type="number"
[value]="2610"
(change)="setHeight(height.value)"
(click)="$event.stopPropagation()"
/>
</button>
<button mat-menu-item (click)="$event.stopPropagation()">
<mat-radio-group [(ngModel)]="fitting" class="fitting-radios">
<mat-radio-button *ngFor="let fitting of fittings" [value]="fitting"
>{{ fitting }}
</mat-radio-button>
</mat-radio-group>
</button>
<button mat-menu-item class="make-picture" (click)="makePicture()">
Create picture
</button>
</mat-menu>
<app-menu-toggle
[matMenuTriggerFor]="menu"
tooltip="Creates picture from current view"
icon="png"
>
</app-menu-toggle>
<div>
<canvas hidden id="screenshotCanvas"></canvas>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
.size-input {
background: transparent;
color: var(--phoenix-text-color);
text-align: center;
}

.size-input label {
width: 60px;
}

.size-input input {
background: transparent;
color: var(--phoenix-text-color);
width: 80px;
}

.make-picture {
text-align: center;
display: block;
}

.make-picture span {
padding: 5px 25px;
border: 2px solid var(--phoenix-text-color);
border-radius: 8px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PhoenixUIModule } from '../../phoenix-ui.module';

import { MakePictureComponent } from './make-picture.component';

describe('MakePictureComponent', () => {
let component: MakePictureComponent;
let fixture: ComponentFixture<MakePictureComponent>;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [PhoenixUIModule],
}).compileComponents();

fixture = TestBed.createComponent(SSModeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
component.ngOnInit();
});

it('should create', () => {
expect(component).toBeTruthy();
});

it('should call toggleCrop on slide change', () => {
const componentDebug = fixture.debugElement;
const slider = componentDebug.query(By.directive(MatSlideToggle));
spyOn(component, 'toggleCrop'); // set your spy
slider.triggerEventHandler('change', null); // triggerEventHandler
expect(component.toggleCrop).toHaveBeenCalled(); // event has been called
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core';
import { FormControl, Validators } from '@angular/forms';
import { EventDisplayService } from '../../../services/event-display.service';

@Component({
selector: 'app-make-picture',
templateUrl: './make-picture.component.html',
styleUrls: ['./make-picture.component.scss'],
encapsulation: ViewEncapsulation.None,
})
export class MakePictureComponent implements OnInit {
fittings: string[] = ['Crop', 'Stretch'];
fitting: string = 'Crop';
width: number = 3840;
height: number = 2160;
constructor(private eventDisplay: EventDisplayService) {}
ngOnInit() {}
setWidth(value) {
this.width = value;
}
setHeight(value) {
this.height = value;
}

makePicture() {
this.eventDisplay.makeScreenShot(this.width, this.height, this.fitting);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@
<!-- Toggle for screenshot mode -->
<app-ss-mode></app-ss-mode>

<!-- Make pictures from scene -->
<app-make-picture></app-make-picture>

<!-- Toggle for loading geometries modal-->
<app-io-options
[eventDataImportOptions]="eventDataImportOptions"
Expand Down

0 comments on commit f2027bb

Please sign in to comment.