Skip to content

Commit f2027bb

Browse files
author
Sebastien Ponce
committed
Added a make-picture component
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.
1 parent 8b924db commit f2027bb

File tree

11 files changed

+280
-0
lines changed

11 files changed

+280
-0
lines changed

packages/phoenix-event-display/src/event-display.ts

+19
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,25 @@ export class EventDisplay {
9696
this.enableKeyboardControls();
9797
}
9898

99+
/**
100+
* Takes a screen shot of the current view
101+
* @param width the width of the picture to be created
102+
* @param height the height of the picture to be created
103+
* @param fitting the type of fitting to use in case width and height
104+
* ratio do not match the current screen ratio. Posible values are
105+
* - Crop : current view is cropped on both side or up and done to fit ratio
106+
* thus it is not streched, but some parts are lost
107+
* - Strech : current view is streched to given format
108+
* this is the default and used also for any other value given to fitting
109+
*/
110+
public makeScreenShot(
111+
width: number,
112+
height: number,
113+
fitting: string = 'Strech'
114+
) {
115+
this.graphicsLibrary.makeScreenShot(width, height, fitting);
116+
}
117+
99118
/**
100119
* Initialize XR.
101120
* @param xrSessionType Type of the XR session. Either AR or VR.

packages/phoenix-event-display/src/managers/three-manager/index.ts

+114
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ import {
1414
MeshBasicMaterial,
1515
Euler,
1616
PerspectiveCamera,
17+
Vector2,
1718
} from 'three';
19+
import html2canvas from 'html2canvas';
1820
import { Configuration } from '../../lib/types/configuration';
1921
import { ControlsManager } from './controls-manager';
2022
import { RendererManager } from './renderer-manager';
@@ -755,6 +757,118 @@ export class ThreeManager {
755757
this.animationsManager.animateClippingWithCollision(tweenDuration, onEnd);
756758
}
757759

760+
saveBlob = (function () {
761+
const a = document.createElement('a');
762+
document.body.appendChild(a);
763+
a.style.display = 'none';
764+
return function saveData(blob, fileName) {
765+
const url = window.URL.createObjectURL(blob);
766+
a.href = url;
767+
a.download = fileName;
768+
a.click();
769+
};
770+
})();
771+
772+
/**
773+
* Takes a screen shot of the current view
774+
* @param width the width of the picture to be created
775+
* @param height the height of the picture to be created
776+
* @param fitting the type of fitting to use in case width and height
777+
* ratio do not match the current screen ratio. Posible values are
778+
* - Crop : current view is cropped on both side or up and done to fit ratio
779+
* thus it is not streched, but some parts are lost
780+
* - Strech : current view is streched to given format
781+
* this is the default and used also for any other value given to fitting
782+
*/
783+
public makeScreenShot(
784+
width: number,
785+
height: number,
786+
fitting: string = 'Strech'
787+
) {
788+
// compute actual size of screen shot, based on current view and reuested size
789+
const mainRenderer = this.rendererManager.getMainRenderer();
790+
var originalSize = new Vector2();
791+
mainRenderer.getSize(originalSize);
792+
var scaledHeight = height;
793+
var scaledWidth = width;
794+
if (fitting == 'Crop') {
795+
// Massage width and height so that we keep the screen ratio
796+
// and thus the image from the screen is not streched
797+
if (originalSize.width * height < originalSize.height * width) {
798+
scaledHeight = (originalSize.height * width) / originalSize.width;
799+
} else {
800+
scaledWidth = (originalSize.width * height) / originalSize.height;
801+
}
802+
}
803+
const heightShift = (scaledHeight - height) / 2;
804+
const widthShift = (scaledWidth - width) / 2;
805+
806+
// get background color to be used
807+
var bkgColor = getComputedStyle(document.body).getPropertyValue(
808+
'--phoenix-background-color'
809+
);
810+
811+
// grab output canvas on which we will draw, and set size
812+
var outputCanvas = document.getElementById(
813+
'screenshotCanvas'
814+
) as HTMLCanvasElement;
815+
outputCanvas.width = width;
816+
outputCanvas.height = height;
817+
var ctx = outputCanvas.getContext('2d');
818+
ctx.fillStyle = bkgColor;
819+
ctx.fillRect(0, 0, width, height);
820+
// draw main image on our output canvas, with right size
821+
mainRenderer.setSize(scaledWidth, scaledHeight, false);
822+
this.render();
823+
ctx.drawImage(
824+
mainRenderer.domElement,
825+
widthShift,
826+
heightShift,
827+
width,
828+
height,
829+
0,
830+
0,
831+
width,
832+
height
833+
);
834+
mainRenderer.setSize(originalSize.width, originalSize.height, false);
835+
this.render();
836+
837+
// Get info panel
838+
const infoPanel = document.getElementById('experimentInfo');
839+
if (infoPanel != null) {
840+
// Compute size of info panel on final picture
841+
const infoHeight =
842+
(infoPanel.clientHeight * scaledHeight) / originalSize.height;
843+
const infoWidth =
844+
(infoPanel.clientWidth * scaledWidth) / originalSize.width;
845+
846+
// Add info panel to output. This is HTML, so first convert it to canvas,
847+
// and then draw to our output canvas
848+
html2canvas(infoPanel, { backgroundColor: bkgColor }).then((canvas) => {
849+
canvas.toBlob((blob) => {
850+
ctx.drawImage(
851+
canvas,
852+
infoHeight / 6,
853+
infoHeight / 6,
854+
infoWidth,
855+
infoHeight
856+
);
857+
// Finally save to png file
858+
outputCanvas.toBlob((blob) => {
859+
const a = document.createElement('a');
860+
document.body.appendChild(a);
861+
a.style.display = 'none';
862+
const url = window.URL.createObjectURL(blob);
863+
a.href = url;
864+
a.download = `screencapture.png`;
865+
a.click();
866+
});
867+
});
868+
});
869+
}
870+
}
871+
758872
/**
759873
* Initialize the VR session.
760874
* @param xrSessionType Type of the XR session. Either AR or VR.

packages/phoenix-ng/projects/phoenix-app/src/app/sections/lhcb/lhcb.component.html

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
<app-vr-toggle></app-vr-toggle>
1919
<app-ar-toggle></app-ar-toggle>
2020
<app-ss-mode></app-ss-mode>
21+
<app-make-picture></app-make-picture>
2122
<app-io-options
2223
[eventDataImportOptions]="eventDataImportOptions"
2324
></app-io-options>
Loading

packages/phoenix-ng/projects/phoenix-ui-components/lib/components/phoenix-ui.module.ts

+6
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ import { CommonModule } from '@angular/common';
33
import { RouterModule } from '@angular/router';
44
import { DragDropModule } from '@angular/cdk/drag-drop';
55
import { OverlayModule } from '@angular/cdk/overlay';
6+
import { FormsModule } from '@angular/forms';
67
import { MatButtonModule } from '@angular/material/button';
78
import { MatCheckboxModule } from '@angular/material/checkbox';
89
import { MatDialogModule } from '@angular/material/dialog';
910
import { MatIconModule } from '@angular/material/icon';
1011
import { MatMenuModule } from '@angular/material/menu';
12+
import { MatRadioModule } from '@angular/material/radio';
1113
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
1214
import { MatSliderModule } from '@angular/material/slider';
1315
import { MatTooltipModule } from '@angular/material/tooltip';
@@ -50,6 +52,7 @@ import {
5052
VrToggleComponent,
5153
ArToggleComponent,
5254
SSModeComponent,
55+
MakePictureComponent,
5356
PerformanceToggleComponent,
5457
ShareLinkComponent,
5558
ShareLinkDialogComponent,
@@ -102,6 +105,7 @@ const PHOENIX_COMPONENTS: Type<any>[] = [
102105
VrToggleComponent,
103106
ArToggleComponent,
104107
SSModeComponent,
108+
MakePictureComponent,
105109
PerformanceToggleComponent,
106110
LoaderComponent,
107111
ShareLinkComponent,
@@ -125,7 +129,9 @@ const PHOENIX_COMPONENTS: Type<any>[] = [
125129
MatButtonModule,
126130
MatTooltipModule,
127131
OverlayModule,
132+
FormsModule,
128133
MatMenuModule,
134+
MatRadioModule,
129135
MatSliderModule,
130136
MatSlideToggleModule,
131137
MatCheckboxModule,

packages/phoenix-ng/projects/phoenix-ui-components/lib/components/ui-menu/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export * from './vr-toggle/vr-toggle.component';
2525
export * from './ar-toggle/ar-toggle.component';
2626
export * from './zoom-controls/zoom-controls.component';
2727
export * from './ss-mode/ss-mode.component';
28+
export * from './make-picture/make-picture.component';
2829
export * from './performance-toggle/performance-toggle.component';
2930
export * from './ui-menu.component';
3031
export * from './share-link/share-link.component';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<mat-menu class="mat-menu" #menu>
2+
<button mat-menu-item (click)="$event.stopPropagation()" class="size-input">
3+
<label>width</label>
4+
<input
5+
#width
6+
mapInput
7+
type="number"
8+
[value]="3840"
9+
(change)="setWidth(width.value)"
10+
(click)="$event.stopPropagation()"
11+
/>
12+
</button>
13+
<button mat-menu-item (click)="$event.stopPropagation()" class="size-input">
14+
<label>height</label>
15+
<input
16+
#height
17+
mapInput
18+
type="number"
19+
[value]="2610"
20+
(change)="setHeight(height.value)"
21+
(click)="$event.stopPropagation()"
22+
/>
23+
</button>
24+
<button mat-menu-item (click)="$event.stopPropagation()">
25+
<mat-radio-group [(ngModel)]="fitting" class="fitting-radios">
26+
<mat-radio-button *ngFor="let fitting of fittings" [value]="fitting"
27+
>{{ fitting }}
28+
</mat-radio-button>
29+
</mat-radio-group>
30+
</button>
31+
<button mat-menu-item class="make-picture" (click)="makePicture()">
32+
Create picture
33+
</button>
34+
</mat-menu>
35+
<app-menu-toggle
36+
[matMenuTriggerFor]="menu"
37+
tooltip="Creates picture from current view"
38+
icon="png"
39+
>
40+
</app-menu-toggle>
41+
<div>
42+
<canvas hidden id="screenshotCanvas"></canvas>
43+
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
.size-input {
2+
background: transparent;
3+
color: var(--phoenix-text-color);
4+
text-align: center;
5+
}
6+
7+
.size-input label {
8+
width: 60px;
9+
}
10+
11+
.size-input input {
12+
background: transparent;
13+
color: var(--phoenix-text-color);
14+
width: 80px;
15+
}
16+
17+
.make-picture {
18+
text-align: center;
19+
display: block;
20+
}
21+
22+
.make-picture span {
23+
padding: 5px 25px;
24+
border: 2px solid var(--phoenix-text-color);
25+
border-radius: 8px;
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { ComponentFixture, TestBed } from '@angular/core/testing';
2+
import { PhoenixUIModule } from '../../phoenix-ui.module';
3+
4+
import { MakePictureComponent } from './make-picture.component';
5+
6+
describe('MakePictureComponent', () => {
7+
let component: MakePictureComponent;
8+
let fixture: ComponentFixture<MakePictureComponent>;
9+
10+
beforeEach(() => {
11+
TestBed.configureTestingModule({
12+
imports: [PhoenixUIModule],
13+
}).compileComponents();
14+
15+
fixture = TestBed.createComponent(SSModeComponent);
16+
component = fixture.componentInstance;
17+
fixture.detectChanges();
18+
component.ngOnInit();
19+
});
20+
21+
it('should create', () => {
22+
expect(component).toBeTruthy();
23+
});
24+
25+
it('should call toggleCrop on slide change', () => {
26+
const componentDebug = fixture.debugElement;
27+
const slider = componentDebug.query(By.directive(MatSlideToggle));
28+
spyOn(component, 'toggleCrop'); // set your spy
29+
slider.triggerEventHandler('change', null); // triggerEventHandler
30+
expect(component.toggleCrop).toHaveBeenCalled(); // event has been called
31+
});
32+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core';
2+
import { FormControl, Validators } from '@angular/forms';
3+
import { EventDisplayService } from '../../../services/event-display.service';
4+
5+
@Component({
6+
selector: 'app-make-picture',
7+
templateUrl: './make-picture.component.html',
8+
styleUrls: ['./make-picture.component.scss'],
9+
encapsulation: ViewEncapsulation.None,
10+
})
11+
export class MakePictureComponent implements OnInit {
12+
fittings: string[] = ['Crop', 'Stretch'];
13+
fitting: string = 'Crop';
14+
width: number = 3840;
15+
height: number = 2160;
16+
constructor(private eventDisplay: EventDisplayService) {}
17+
ngOnInit() {}
18+
setWidth(value) {
19+
this.width = value;
20+
}
21+
setHeight(value) {
22+
this.height = value;
23+
}
24+
25+
makePicture() {
26+
this.eventDisplay.makeScreenShot(this.width, this.height, this.fitting);
27+
}
28+
}

packages/phoenix-ng/projects/phoenix-ui-components/lib/components/ui-menu/ui-menu.component.html

+3
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@
5050
<!-- Toggle for screenshot mode -->
5151
<app-ss-mode></app-ss-mode>
5252

53+
<!-- Make pictures from scene -->
54+
<app-make-picture></app-make-picture>
55+
5356
<!-- Toggle for loading geometries modal-->
5457
<app-io-options
5558
[eventDataImportOptions]="eventDataImportOptions"

0 commit comments

Comments
 (0)