diff --git a/App.js b/App.js
index b245738..1641a3b 100644
--- a/App.js
+++ b/App.js
@@ -7,6 +7,7 @@ import {hdf5Loader} from './MincLoader';
import {Viewer} from './Viewer';
import {Login} from './Login';
import * as SecureStore from 'expo-secure-store';
+import * as ScreenOrientation from 'expo-screen-orientation';
async function getValueFor(key) {
return await SecureStore.getItemAsync(key);
@@ -18,6 +19,24 @@ export default function App() {
const [rawData, setRawData] = useState(null);
const [headerData, setHeaderData] = useState(null);
const [shouldScroll, setShouldScroll] = useState(true);
+ const [screenOrientation, setScreenOrientation] = useState(0);
+
+ useEffect(() => {
+ checkOrientation();
+ const subscription = ScreenOrientation.addOrientationChangeListener(
+ handleOrientationChange
+ );
+ return () => {
+ ScreenOrientation.removeOrientationChangeListeners(subscription);
+ };
+ }, []);
+ const checkOrientation = async () => {
+ const orientation = await ScreenOrientation.getOrientationAsync();
+ setScreenOrientation(orientation);
+ };
+ const handleOrientationChange = (o) => {
+ setScreenOrientation(o.orientationInfo.orientation);
+ };
useEffect(() => {
if (token)
@@ -64,11 +83,7 @@ export default function App() {
return (
// ScrollView
-
-
-
-
-
+
-
- setShouldScroll(false) }
onGestureEnd={ () => setShouldScroll(true) }
+ screenOrientation={screenOrientation}
/>
+
-
-
);
@@ -99,7 +117,8 @@ export default function App() {
const styles = StyleSheet.create({
container: {
- flex: 1,
+ display: 'flex',
backgroundColor: '#fff',
+ marginTop: 40,
},
});
diff --git a/PlaneViewer.js b/PlaneViewer.js
index ecd31ed..f1591ed 100644
--- a/PlaneViewer.js
+++ b/PlaneViewer.js
@@ -1,9 +1,8 @@
-import React, {useMemo, useRef, useState, useCallback, useEffect} from 'react';
-import { Gesture, GestureDetector} from 'react-native-gesture-handler';
-import { Pressable, View, Text, PanResponder } from 'react-native';
-import { GLView } from 'expo-gl';
-import Expo2DContext from "expo-2d-context";
-import { SegmentSlider } from './SegmentSlider';
+import React, {useCallback, useEffect, useMemo, useState} from 'react';
+import {Gesture, GestureDetector} from 'react-native-gesture-handler';
+import {Text, View} from 'react-native';
+import {GLView} from 'expo-gl';
+import {SegmentSlider} from './SegmentSlider';
function getScreenPlanes(plane, headers) {
switch (plane) {
@@ -35,9 +34,8 @@ function loadIntensityTexture(gl, headers, data) {
gl.bindTexture(gl.TEXTURE_3D, texture);
const values = new Uint8Array(data.floats.length);
- for(i = 0; i < data.floats.length; i++) {
- const val = ((data.floats[i] -data.min) / (data.max -data.min)) * 255;
- values[i] = val;
+ for(let i = 0; i < data.floats.length; i++) {
+ values[i] = ((data.floats[i] - data.min) / (data.max - data.min)) * 255;
}
// Set the parameters so we can render any size image.
@@ -68,11 +66,12 @@ function useGLCanvas(viewWidth, viewHeight, headers, data, plane) {
const [offsetPos, setOffsetPos] = useState({x: 0, y: 0});
const [zoomUniform, setZoomUniform] = useState(null);
const [viewOffsetUniform, setViewOffsetUniform] = useState(null);
+ const [pixelsPerUnit, setPixelsPerUnit] = useState(0);
const draw = useCallback(() => {
if (!gl) {
console.warn('No gl in draw');
return;
- }
+ }
// We draw the 1 square which consists of 2 triangles
// covering the whole viewport. The program set up
// a_position in onContextCreate
@@ -80,7 +79,23 @@ function useGLCanvas(viewWidth, viewHeight, headers, data, plane) {
gl.flush();
gl.endFrameEXP();
}, [gl]);
- const setZoomFactorCB = useCallback( (newZoom) => {
+
+ // Force re-render on viewWidth change
+ useEffect(() => {
+ if (!gl)
+ return;
+ gl.drawingBufferWidth = Math.ceil(viewWidth * pixelsPerUnit);
+ gl.drawingBufferHeight = Math.ceil(viewHeight * pixelsPerUnit);
+ gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
+ // TODO: Replace with better alternative
+ setTimeout(() => {
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ requestAnimationFrame(draw);
+ gl.endFrameEXP();
+ }, 100);
+ }, [viewWidth]);
+
+ const setZoomFactorCB = useCallback( (newZoom) => {
if (!gl) {
console.log('No gl for setZoomFactorCB');
return;
@@ -129,7 +144,7 @@ function useGLCanvas(viewWidth, viewHeight, headers, data, plane) {
gl.compileShader(shader);
const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if (!success) {
- msg = gl.getShaderInfoLog(shader);
+ const msg = gl.getShaderInfoLog(shader);
gl.deleteShader(shader);
throw new Error("Could not compile shader:" + msg);
}
@@ -138,8 +153,8 @@ function useGLCanvas(viewWidth, viewHeight, headers, data, plane) {
const linkProgram = (vertex, fragment) => {
const program = gl.createProgram();
- gl.attachShader(program, vert);
- gl.attachShader(program, frag);
+ gl.attachShader(program, vertex);
+ gl.attachShader(program, fragment);
gl.linkProgram(program);
const success = gl.getProgramParameter(program, gl.LINK_STATUS);
if (!success) {
@@ -151,8 +166,8 @@ function useGLCanvas(viewWidth, viewHeight, headers, data, plane) {
};
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
+ setPixelsPerUnit(gl.drawingBufferWidth / viewWidth);
gl.clearColor(0, 1, 0, 1);
-
// Create the vertex shader (position & size)
const vert = compileShader(gl.VERTEX_SHADER,
`
@@ -342,7 +357,6 @@ function useGLCanvas(viewWidth, viewHeight, headers, data, plane) {
0,
);
-
// Calculate display uniforms
const spaceSizeUniformLocation = gl.getUniformLocation(program, "u_spacesize");
if (spaceSizeUniformLocation) {
@@ -438,10 +452,14 @@ function useGLCanvas(viewWidth, viewHeight, headers, data, plane) {
gl.uniform2f(crosshairsUniform, crosshairs.x, crosshairs.y);
draw();
}, [gl, draw, crosshairsUniform]);
- const canvas = useRef().current;
- return {
+
+ const canvas = useMemo(() => {
+ return ;
+ }, [viewWidth]);
+
+ return {
setSliceNum: setSliceNum,
setCrosshairs: setCrosshairs,
canvas: canvas,
@@ -486,15 +504,12 @@ function calculateTouchPos(plane, headers, sliceNo, zoomFactor, canvasSize, x, y
}
}
-export function PlaneViewer({headers, data, plane, SliceNo, label, onSliceChange, crosshairs, setPosition, onGestureStart, onGestureEnd}) {
- // FIXME: Width and height shouldn't be fixed values
- const canvasSize = {x: 350, y: 400};
+export function PlaneViewer({headers, data, plane, SliceNo, label, onSliceChange, crosshairs, setPosition, onGestureStart, onGestureEnd, viewWidth}) {
+ const canvasSize = {x: viewWidth, y: viewWidth * 8 / 7};
const {setSliceNum, canvas, setCrosshairs, zoomFactor, scaleZoomFactor, setZoomFactor, panViewport, setViewOffset} = useGLCanvas(canvasSize.x, canvasSize.y, headers, data, plane);
const onSliderChange = useCallback( (newValue) => {
onSliceChange(newValue);
-
setSliceNum(newValue);
-
}, [setSliceNum]);
const gestures = useMemo( () => {
@@ -576,14 +591,19 @@ export function PlaneViewer({headers, data, plane, SliceNo, label, onSliceChange
throw new Error("Invalid plane");
}
return (
-
-
+
{label} (Size: {sliderMax})
{canvas}
);
diff --git a/SegmentSlider.js b/SegmentSlider.js
index b08b26b..cf5a296 100644
--- a/SegmentSlider.js
+++ b/SegmentSlider.js
@@ -1,11 +1,10 @@
import React from 'react';
-import { View, Text, Dimensions } from 'react-native';
+import { View, Text } from 'react-native';
import Slider from '@react-native-community/slider';
export function SegmentSlider(props) {
// Calculate the width based on the screen dimensions
- const { width } = Dimensions.get('window');
- const sliderWidth = width * 0.9; // 70% of screen width
+ const sliderWidth = props.viewWidth * 1.1;
const label = props.label ? {props.label} : null;
return (
diff --git a/Viewer.js b/Viewer.js
index e434376..fa7fa58 100644
--- a/Viewer.js
+++ b/Viewer.js
@@ -1,8 +1,5 @@
import React, {useState, useEffect} from 'react';
-import { Pressable, View, Text } from 'react-native';
-import { GLView } from 'expo-gl';
-import Expo2DContext from "expo-2d-context";
-import { SegmentSlider } from './SegmentSlider';
+import { Dimensions, View, Text } from 'react-native';
import {PlaneViewer} from './PlaneViewer';
@@ -31,9 +28,18 @@ function preprocess(rawdata) {
}
}
-export function Viewer({rawData, headers, onGestureStart, onGestureEnd}) {
+export function Viewer({rawData, headers, onGestureStart, onGestureEnd, screenOrientation}) {
const [data, setData] = useState(null);
const [coord, setCoord] = useState({x: 0, y: 0, z: 0});
+ const [viewWidth, setViewWidth] = useState(0);
+
+ useEffect(() => {
+ setViewWidth(screenOrientation < 3
+ ? Dimensions.get('window').width * 0.85 // Portrait
+ : Dimensions.get('window').width * 0.3 // Landscape
+ );
+ }, [screenOrientation]);
+
useEffect( () => {
if (!rawData) {
return;
@@ -63,7 +69,12 @@ export function Viewer({rawData, headers, onGestureStart, onGestureEnd}) {
// This was reached by trial and error with 1 sample file and
// is not reliable
return (
-
+
+ viewWidth={viewWidth}
+ />
+ viewWidth={viewWidth}
+ />
);
}
diff --git a/app.json b/app.json
index 850a088..e4a5cf4 100644
--- a/app.json
+++ b/app.json
@@ -3,7 +3,7 @@
"name": "BrainViewer",
"slug": "BrainViewer",
"version": "1.0.0",
- "orientation": "portrait",
+ "orientation": "default",
"icon": "./assets/icon.png",
"userInterfaceStyle": "light",
"splash": {
diff --git a/package-lock.json b/package-lock.json
index d00fa6e..0135039 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,6 +13,7 @@
"expo": "~48.0.18",
"expo-2d-context": "^0.0.3",
"expo-gl": "~12.4.0",
+ "expo-screen-orientation": "~5.1.1",
"expo-secure-store": "~12.1.1",
"expo-status-bar": "~1.4.4",
"pako": "^2.1.0",
@@ -7449,6 +7450,14 @@
"invariant": "^2.2.4"
}
},
+ "node_modules/expo-screen-orientation": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/expo-screen-orientation/-/expo-screen-orientation-5.1.1.tgz",
+ "integrity": "sha512-rYpHUwHpuKlEErxupl3wM+KWcySrSJTXb5Une5gDINMmjWbLBx0N1P2MG1aSPA2eT7bShTO/xDJyegjf438l8w==",
+ "peerDependencies": {
+ "expo": "*"
+ }
+ },
"node_modules/expo-secure-store": {
"version": "12.1.1",
"resolved": "https://registry.npmjs.org/expo-secure-store/-/expo-secure-store-12.1.1.tgz",
diff --git a/package.json b/package.json
index ec7052a..2d5c9e2 100644
--- a/package.json
+++ b/package.json
@@ -20,7 +20,8 @@
"react": "18.2.0",
"react-native": "0.71.8",
"three": "^0.154.0",
- "react-native-gesture-handler": "~2.9.0"
+ "react-native-gesture-handler": "~2.9.0",
+ "expo-screen-orientation": "~5.1.1"
},
"devDependencies": {
"@babel/core": "^7.20.0"