2545 lines
83 KiB
C++
2545 lines
83 KiB
C++
#ifdef __EMSCRIPTEN__
|
|
//#include <emscripten.h>
|
|
#include <emscripten/emscripten.h>
|
|
|
|
#else
|
|
#include <string> // for std::string
|
|
#include <fstream> // for std::ifstream
|
|
#include <vector> // for std::vector
|
|
#endif
|
|
|
|
#include <cstring>
|
|
|
|
#include "nlohmann/json.hpp" // 라이브러리 경로에 맞게 수정
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
#include <SDL2/SDL.h>
|
|
#include <SDL2/SDL_opengles2.h>
|
|
#include <SDL2/SDL_opengles2_gl2ext.h>
|
|
|
|
#include <dcmtk/ofstd/ofstring.h>
|
|
#include "dcmtk/config/osconfig.h"
|
|
#include "dcmtk/dcmdata/dcfilefo.h"
|
|
#include "dcmtk/dcmdata/dcdeftag.h"
|
|
#include "dcmtk/dcmdata/dcuid.h"
|
|
#include "dcmtk/dcmdata/dcmetinf.h"
|
|
#include "dcmtk/dcmdata/dcdict.h"
|
|
#include "dcmtk/dcmdata/dcdicent.h"
|
|
#include "dcmtk/dcmdata/dcxfer.h"
|
|
|
|
#include "dcmtk/dcmjpeg/djdecode.h" /* for JPEG decoders */
|
|
#include "dcmtk/dcmjpeg/djencode.h" /* for JPEG encoders */
|
|
#include "dcmtk/dcmjpls/djdecode.h" /* for JPEG-LS decoders */
|
|
#include "dcmtk/dcmjpls/djencode.h" /* for JPEG-LS encoders */
|
|
#include "dcmtk/dcmdata/dcrledrg.h" /* for RLE decoder */
|
|
#include "dcmtk/dcmdata/dcrleerg.h" /* for RLE encoder */
|
|
#include "dcmtk/dcmjpeg/dipijpeg.h" /* for dcmimage JPEG plugin */
|
|
#include "dcmtk/dcmjpeg/djrplol.h"
|
|
|
|
|
|
#include "dcmtk/dcmimage/diregist.h"
|
|
#include "dcmtk/dcmdata/dcpixseq.h"
|
|
#include "dcmtk/dcmjpeg/djcparam.h"
|
|
#include "dcmtk/dcmjpeg/djeijg8.h"
|
|
#include "dcmtk/dcmjpeg/djcodece.h"
|
|
#include "dcmtk/dcmdata/dcchrstr.h"
|
|
#include "dcmtk/ofstd/ofchrenc.h"
|
|
|
|
#include "dcmtk/dcmdata/dcvrui.h"
|
|
#include "dcmtk/dcmdata/dcvrsh.h"
|
|
#include "dcmtk/dcmdata/dcistrmb.h"
|
|
#include "dcmtk/oflog/loglevel.h"
|
|
|
|
#include "events.h"
|
|
|
|
#include "unistd.h"
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
//emscripten::val json;
|
|
|
|
int g_currentMode = (int)InteractionMode::MODE_PAN; // 기본 모드는 Pan
|
|
|
|
typedef void (*callback_UpdateDcmImage) (int nRet);
|
|
callback_UpdateDcmImage callback_UpdateDcmImageComplete = NULL;
|
|
|
|
typedef void (*callback_UpdateWasmInitGeometry) (const char* pStr);
|
|
callback_UpdateWasmInitGeometry callback_UpdateWasmInitGeometryComplete = NULL;
|
|
|
|
|
|
EventHandler* g_pEventHandler = NULL;
|
|
|
|
int g_nHandleWget = 0;
|
|
bool g_bCancelLoad = false;
|
|
|
|
bool g_bLoadImage = false;
|
|
|
|
bool g_bVisible = false;
|
|
|
|
DcmDataset* g_pDcmDataset = NULL;
|
|
DcmElement* g_pixelDataElement = NULL;
|
|
bool g_bChange = false;
|
|
|
|
|
|
GLuint g_nTextureID = 0;
|
|
GLuint g_nTextureGray8ID = 0;
|
|
GLuint g_nTextureGray16ID = 0;
|
|
GLuint g_nTextureGray8PaletteID = 0;
|
|
GLuint g_nPaletteTextureID = 0;
|
|
|
|
GLuint g_shaderProgramGray8 = 0;
|
|
GLuint g_shaderProgramGray8Palette = 0;
|
|
GLuint g_shaderProgramGray16 = 0;
|
|
GLuint g_shaderProgramRGB = 0;
|
|
|
|
// Vertex shader
|
|
GLint g_shaderPanGray16, g_shaderZoomGray16, g_shaderAspectGray16, g_shaderWindowCenterGray16, g_shaderWindowWidthGray16;
|
|
GLint g_shaderPanGray8, g_shaderZoomGray8, g_shaderAspectGray8, g_shaderWindowCenterGray8, g_shaderWindowWidthGray8;
|
|
GLint g_shaderPanGray8Palette, g_shaderZoomGray8Palette, g_shaderAspectGray8Palette, g_shaderWindowCenterGray8Palette, g_shaderWindowWidthGray8Palette;
|
|
GLint g_shaderPanRGB, g_shaderZoomRGB, g_shaderAspectRGB, g_shaderWindowCenterRGB, g_shaderWindowWidthRGB;
|
|
|
|
int g_nTotalBytes = 0;
|
|
int g_nReadBytes = 0;
|
|
|
|
int g_nWindowWidth = 0;
|
|
int g_nWindowCenter = 0;
|
|
|
|
int g_nPrevWindowWidth = 0;
|
|
int g_nPrevWindowCenter = 0;
|
|
|
|
int g_nDefaultWindowWidth = 0;
|
|
int g_nDefaultWindowCenter = 0;
|
|
|
|
int g_nColorType = 0;
|
|
|
|
Uint32 g_nCurrentFrame = 0;
|
|
int g_nTotalFrames = 0;
|
|
|
|
int g_nFrameTextureWidth = 0;
|
|
int g_nFrameTextureHeight = 0;
|
|
|
|
int g_nSamplesPerPixel = 0;
|
|
int g_nBitsAllocated = 0;
|
|
|
|
int g_nFrameSizeUncompressed = 0;
|
|
|
|
int g_nWindowSizeWidth = 0;
|
|
int g_nWindowSizeHeight = 0;
|
|
|
|
GLuint g_nUsePaletteIndex = 0; // 팔레트 모드 사용 여부
|
|
GLuint g_nPaletteTexLocationIndex = 0; // 팔레트 텍스처 유니폼 변수
|
|
|
|
float g_fFrameTime = 0;
|
|
float g_fFrameDelay = 0;
|
|
|
|
|
|
int g_nPaletteEntries = 0;
|
|
|
|
std::string g_photometricInterpretation;
|
|
|
|
//char* g_pBuf = NULL;
|
|
GLuint g_vbo = 0;
|
|
|
|
bool g_bUseFirst = false;
|
|
|
|
Uint32 g_lastUpdateTime;
|
|
|
|
std::string strRet;
|
|
|
|
int g_nViewerID = -1;
|
|
|
|
|
|
//std::vector<std::vector<char>> g_cachedFrames;
|
|
uint8_t* g_cachedFrames = NULL;
|
|
|
|
//std::vector<unsigned char> g_paletteBuffer;
|
|
uint8_t* g_paletteBuffer = NULL;
|
|
|
|
|
|
const GLchar* vertexSourceGray8 =
|
|
"uniform vec2 pan; \n"
|
|
"uniform float zoom; \n"
|
|
"uniform float aspect; \n"
|
|
"attribute vec4 position; \n"
|
|
"attribute vec2 a_texCoord; \n"
|
|
"varying vec2 texCoord; \n"
|
|
"void main() \n"
|
|
"{ \n"
|
|
" gl_Position = vec4(position.xyz, 1.0); \n"
|
|
" gl_Position.xy += pan; \n"
|
|
" gl_Position.xy *= zoom; \n"
|
|
" texCoord = a_texCoord; \n"
|
|
" gl_Position.y *= aspect; \n"
|
|
"} \n";
|
|
|
|
|
|
// Fragment/pixel shader
|
|
const GLchar* fragmentSourceGray8 =
|
|
"precision mediump float; \n"
|
|
"varying vec2 texCoord; \n"
|
|
"uniform sampler2D texSampler; \n"
|
|
"uniform float fWindowCenter; \n"
|
|
"uniform float fWindowWidth; \n"
|
|
"\n"
|
|
"void main() \n"
|
|
"{ \n"
|
|
" // 1. 텍스처에서 8비트 회색조 값을 읽어옵니다. \n"
|
|
" // GL_LUMINANCE 포맷 텍스처의 경우, r, g, b 채널에 모두 동일한 값이 들어있습니다. \n"
|
|
" // texture2D 함수는 이 값을 0.0 ~ 1.0 범위의 실수로 반환합니다. \n"
|
|
" float normalizedPixel = texture2D(texSampler, texCoord).r;\n"
|
|
"\n"
|
|
" // 2. 0.0~1.0 범위의 값을 원래의 8비트 값(0~255) 범위로 되돌립니다. \n"
|
|
" float originalValue = normalizedPixel * 255.0; \n"
|
|
"\n"
|
|
" // 3. Window Level/Width를 적용하여 최종 밝기(0.0 ~ 1.0)를 계산합니다. \n"
|
|
" float windowMin = fWindowCenter - fWindowWidth / 2.0; \n"
|
|
" float finalValue = (originalValue - windowMin) / fWindowWidth; \n"
|
|
"\n"
|
|
" // 4. 계산된 값을 0.0 ~ 1.0 범위로 제한(clamp)하여 색상 값으로 사용합니다. \n"
|
|
" finalValue = clamp(finalValue, 0.0, 1.0); \n"
|
|
"\n"
|
|
" // 5. 최종 색상(회색조)을 모든 채널(R, G, B)에 동일하게 적용하여 출력합니다. \n"
|
|
" gl_FragColor = vec4(finalValue, finalValue, finalValue, 1.0); \n"
|
|
"} \n";
|
|
|
|
|
|
// Palette Color용 프래그먼트 셰이더
|
|
const GLchar* fragmentSourceGray8Palette =
|
|
"precision mediump float; \n"
|
|
"varying vec2 texCoord; \n"
|
|
"uniform sampler2D texSampler; \n"
|
|
"uniform sampler2D u_paletteTexture; \n"
|
|
"uniform float fWindowCenter; \n"
|
|
"uniform float fWindowWidth; \n"
|
|
"uniform bool u_usePalette; \n"
|
|
"\n"
|
|
"void main() \n"
|
|
"{ \n"
|
|
" if (u_usePalette) \n"
|
|
" { \n"
|
|
" // === 팔레트 컬러 디스플레이 모드 === \n"
|
|
"\n"
|
|
" // 1. texSampler에서 현재 픽셀의 '인덱스' 값을 읽어옵니다. \n"
|
|
" float index = texture2D(texSampler, texCoord).r; \n"
|
|
"\n"
|
|
" // 2. 이 인덱스를 좌표로 사용하여 u_paletteTexture에서 최종 색상을 '조회'합니다. \n"
|
|
" vec4 finalColor = texture2D(u_paletteTexture, vec2(index, 0.5)); \n"
|
|
"\n"
|
|
" // 3. 조회된 팔레트 색상을 그대로 화면에 출력합니다. \n"
|
|
" gl_FragColor = finalColor; \n"
|
|
" } \n"
|
|
" else \n"
|
|
" { \n"
|
|
" // === 그레이스케일 + Window/Level 디스플레이 모드 === \n"
|
|
"\n"
|
|
" // 1. texSampler에서 현재 픽셀의 '회색조 값'을 읽어옵니다. \n"
|
|
" float normalizedPixel = texture2D(texSampler, texCoord).r;\n"
|
|
"\n"
|
|
" // 2. 0.0~1.0 범위의 값을 원래의 0~255 범위로 되돌립니다. \n"
|
|
" float originalValue = normalizedPixel * 255.0; \n"
|
|
"\n"
|
|
" // 3. Window/Level을 적용하여 최종 밝기를 계산합니다. \n"
|
|
" float windowMin = fWindowCenter - fWindowWidth / 2.0; \n"
|
|
" float finalValue = (originalValue - windowMin) / fWindowWidth; \n"
|
|
"\n"
|
|
" // 4. 계산된 값을 0.0 ~ 1.0 범위로 제한합니다. \n"
|
|
" finalValue = clamp(finalValue, 0.0, 1.0); \n"
|
|
"\n"
|
|
" // 5. 최종 계산된 회색조 색상을 출력합니다. \n"
|
|
" gl_FragColor = vec4(finalValue, finalValue, finalValue, 1.0); \n"
|
|
" } \n"
|
|
"} \n";
|
|
|
|
|
|
// Palette Color용 프래그먼트 셰이더
|
|
const GLchar* fragmentSourceGray8Palette_v1 =
|
|
"precision mediump float; \n"
|
|
"varying vec2 texCoord; \n"
|
|
"uniform sampler2D texSampler; \n"
|
|
"uniform sampler2D u_paletteTexture; \n"
|
|
"uniform float fWindowCenter; \n"
|
|
"uniform float fWindowWidth; \n"
|
|
"uniform bool u_usePalette; \n"
|
|
"\n"
|
|
"void main() \n"
|
|
"{ \n"
|
|
" // 1. 텍스처에서 8비트 회색조 값을 읽어옵니다. \n"
|
|
" // GL_LUMINANCE 포맷 텍스처의 경우, r, g, b 채널에 모두 동일한 값이 들어있습니다. \n"
|
|
" // texture2D 함수는 이 값을 0.0 ~ 1.0 범위의 실수로 반환합니다. \n"
|
|
" float normalizedPixel = texture2D(texSampler, texCoord).r;\n"
|
|
"\n"
|
|
" // 2. 0.0~1.0 범위의 값을 원래의 8비트 값(0~255) 범위로 되돌립니다. \n"
|
|
" float originalValue = normalizedPixel * 255.0; \n"
|
|
"\n"
|
|
" // 3. Window Level/Width를 적용하여 최종 밝기(0.0 ~ 1.0)를 계산합니다. \n"
|
|
" float windowMin = fWindowCenter - fWindowWidth / 2.0; \n"
|
|
" float finalValue = (originalValue - windowMin) / fWindowWidth; \n"
|
|
"\n"
|
|
" // 4. 계산된 값을 0.0 ~ 1.0 범위로 제한(clamp)하여 색상 값으로 사용합니다. \n"
|
|
" finalValue = clamp(finalValue, 0.0, 1.0); \n"
|
|
"\n"
|
|
" // 5. 최종 색상(회색조)을 모든 채널(R, G, B)에 동일하게 적용하여 출력합니다. \n"
|
|
" gl_FragColor = vec4(finalValue, finalValue, finalValue, 1.0); \n"
|
|
" if (u_usePalette) \n"
|
|
" { \n"
|
|
" // === 팔레트 컬러 디스플레이 모드 === \n"
|
|
"\n"
|
|
" // 1. texSampler에서 현재 픽셀의 '인덱스' 값을 읽어옵니다. \n"
|
|
" float index = texture2D(texSampler, texCoord).r; \n"
|
|
"\n"
|
|
" // 2. 이 인덱스를 좌표로 사용하여 u_paletteTexture에서 최종 색상을 '조회'합니다. \n"
|
|
" vec4 finalColor = texture2D(u_paletteTexture, vec2(index, 0.5)); \n"
|
|
"\n"
|
|
" // 3. 조회된 팔레트 색상을 그대로 화면에 출력합니다. \n"
|
|
" gl_FragColor = finalColor; \n"
|
|
" } \n"
|
|
"} \n";
|
|
|
|
|
|
|
|
|
|
const GLchar* vertexSourceGray16 =
|
|
"uniform vec2 pan; \n"
|
|
"uniform float zoom; \n"
|
|
"uniform float aspect; \n"
|
|
"attribute vec4 position; \n"
|
|
"attribute vec2 a_texCoord; \n"
|
|
"varying vec2 texCoord; \n"
|
|
"void main() \n"
|
|
"{ \n"
|
|
" gl_Position = vec4(position.xyz, 1.0); \n"
|
|
" gl_Position.xy += pan; \n"
|
|
" gl_Position.xy *= zoom; \n"
|
|
" texCoord = a_texCoord; \n"
|
|
" gl_Position.y *= aspect; \n"
|
|
"} \n";
|
|
|
|
// 16비트 LUMINANCE_ALPHA 텍스처를 올바르게 처리하도록 수정된 프래그먼트 셰이더
|
|
const GLchar* fragmentSourceGray16_test =
|
|
"precision mediump float; \n"
|
|
"varying vec2 texCoord; \n"
|
|
"uniform sampler2D texSampler; \n"
|
|
"uniform float fWindowCenter; \n"
|
|
"uniform float fWindowWidth; \n"
|
|
"\n"
|
|
"void main() \n"
|
|
"{ \n"
|
|
" // 1. 텍스처에서 두 개의 8비트 채널(Luminance, Alpha) 값을 읽어옵니다. \n"
|
|
" vec4 colorOut = texture2D(texSampler, texCoord); \n"
|
|
" gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0); \n"
|
|
"} \n";
|
|
|
|
|
|
|
|
// 16비트 LUMINANCE_ALPHA 텍스처를 올바르게 처리하도록 수정된 프래그먼트 셰이더
|
|
const GLchar* fragmentSourceGray16 =
|
|
"precision mediump float; \n"
|
|
"varying vec2 texCoord; \n"
|
|
"uniform sampler2D texSampler; \n"
|
|
"uniform float fWindowCenter; \n"
|
|
"uniform float fWindowWidth; \n"
|
|
"\n"
|
|
"void main() \n"
|
|
"{ \n"
|
|
" // 1. 텍스처에서 두 개의 8비트 채널(Luminance, Alpha) 값을 읽어옵니다. \n"
|
|
" vec4 colorOut = texture2D(texSampler, texCoord); \n"
|
|
"\n"
|
|
" // 2. 두 채널을 조합하여 원래의 16비트 픽셀 값을 복원합니다. \n"
|
|
" // colorOut.r (Luminance)가 하위 8비트, colorOut.a (Alpha)가 상위 8비트입니다. \n"
|
|
" float highByte = floor(colorOut.a * 255.0); \n"
|
|
" float lowByte = floor(colorOut.r * 255.0); \n"
|
|
" float originalValue = highByte * 256.0 + lowByte; \n"
|
|
"\n"
|
|
" // 3. Window/Level을 적용하여 최종 밝기(0.0 ~ 1.0)를 계산합니다. \n"
|
|
" float windowMin = fWindowCenter - fWindowWidth / 2.0; \n"
|
|
" float normalizedValue = (originalValue - windowMin) / fWindowWidth; \n"
|
|
"\n"
|
|
" // 4. 계산된 값을 0.0 ~ 1.0 범위로 제한(clamp)합니다. \n"
|
|
" normalizedValue = clamp(normalizedValue, 0.0, 1.0); \n"
|
|
"\n"
|
|
" // 5. 최종 색상(회색조)을 출력합니다. \n"
|
|
" gl_FragColor = vec4(normalizedValue, normalizedValue, normalizedValue, 1.0); \n"
|
|
"} \n";
|
|
|
|
|
|
const GLchar* vertexSourceRGB =
|
|
"uniform vec2 panRGB; \n"
|
|
"uniform float zoomRGB; \n"
|
|
"uniform float aspectRGB; \n"
|
|
"attribute vec4 position; \n"
|
|
"attribute vec2 a_texCoord; \n"
|
|
"varying vec2 texCoord; \n"
|
|
"void main() \n"
|
|
"{ \n"
|
|
" gl_Position = vec4(position.xyz, 1.0); \n"
|
|
" gl_Position.xy += panRGB; \n"
|
|
" gl_Position.xy *= zoomRGB; \n"
|
|
" texCoord = a_texCoord; \n"
|
|
" gl_Position.y *= aspectRGB; \n"
|
|
"} \n";
|
|
|
|
|
|
// Fragment/pixel shader
|
|
const GLchar* fragmentSourceRGB =
|
|
"precision mediump float; \n"
|
|
"varying vec2 texCoord; \n"
|
|
"uniform sampler2D texSampler; \n"
|
|
"uniform float fWindowCenterRGB; \n"
|
|
"uniform float fWindowWidthRGB; \n"
|
|
"void main() \n"
|
|
"{ \n"
|
|
" vec4 colorOut = texture2D(texSampler, texCoord); \n"
|
|
" float fMin = fWindowCenterRGB - fWindowWidthRGB/2.0;\n"
|
|
" float fMax = fWindowCenterRGB + fWindowWidthRGB/2.0;\n"
|
|
" float fTestValue = (fMax - fMin);\n"
|
|
" float fR = (colorOut.r*256.0 - fMin) / (fMax-fMin);\n"
|
|
" float fG = (colorOut.g*256.0 - fMin) / (fMax-fMin);\n"
|
|
" float fB = (colorOut.b*256.0 - fMin) / (fMax-fMin);\n"
|
|
" gl_FragColor = vec4(fR, fG, fB, 1.0); \n"
|
|
//" gl_FragColor = vec4(colorOut.r/fTestValue, 1.0, 1.0, 1.0); \n"
|
|
|
|
"} \n";
|
|
|
|
/*
|
|
void ClearValue()
|
|
{
|
|
g_nTotalFrames = 0;
|
|
g_nFrameTextureWidth = 0;
|
|
g_nFrameTextureHeight = 0;
|
|
g_nSamplesPerPixel = 0;
|
|
g_nBitsAllocated = 0;
|
|
g_nCurrentFrame = 0;
|
|
g_nDefaultWindowWidth = 0;
|
|
g_nDefaultWindowCenter = 0;
|
|
g_nWindowWidth = 0;
|
|
g_nWindowCenter = 0;
|
|
g_nPrevWindowWidth = 0;
|
|
g_nPrevWindowCenter = 0;
|
|
g_nColorType = -1;
|
|
g_nFrameSizeUncompressed = 0;
|
|
g_nUsePaletteIndex = 0;
|
|
g_nPaletteTexLocationIndex = 0;
|
|
g_fFrameTime = 0;
|
|
g_fFrameDelay = 0;
|
|
g_nPaletteEntries = 0;
|
|
|
|
g_photometricInterpretation.clear();
|
|
g_paletteBuffer.clear();
|
|
|
|
if(g_pDcmDataset!=NULL)
|
|
{
|
|
delete g_pDcmDataset;
|
|
g_pDcmDataset = NULL;
|
|
}
|
|
|
|
if(g_pBuf!=NULL)
|
|
{
|
|
delete[] g_pBuf;
|
|
g_pBuf = NULL;
|
|
}
|
|
|
|
|
|
g_bChange = false;
|
|
}
|
|
|
|
void ClearTextures()
|
|
{
|
|
if(g_nTextureID>0)
|
|
{
|
|
glDeleteTextures(1, &g_nTextureID);
|
|
g_nTextureID = 0;
|
|
}
|
|
|
|
if(g_nTextureGray8ID>0)
|
|
{
|
|
glDeleteTextures(1, &g_nTextureGray8ID);
|
|
g_nTextureGray8ID = 0;
|
|
}
|
|
|
|
|
|
if(g_nTextureGray8PaletteID>0)
|
|
{
|
|
glDeleteTextures(1, &g_nTextureGray8PaletteID);
|
|
g_nTextureGray8PaletteID = 0;
|
|
}
|
|
|
|
if(g_nTextureGray16ID>0)
|
|
{
|
|
glDeleteTextures(1, &g_nTextureGray16ID);
|
|
g_nTextureGray16ID = 0;
|
|
}
|
|
|
|
if(g_nPaletteTextureID>0)
|
|
{
|
|
glDeleteTextures(1, &g_nPaletteTextureID);
|
|
g_nPaletteTextureID = 0;
|
|
}
|
|
|
|
}
|
|
*/
|
|
|
|
|
|
void PrintError()
|
|
{
|
|
int nError = 0;
|
|
nError = glGetError();
|
|
|
|
if(nError>0)
|
|
{
|
|
printf("GL Error Code: %d\n", nError);
|
|
int a=0;
|
|
}
|
|
}
|
|
|
|
// 셰이더 컴파일 성공 여부를 확인하고 에러 로그를 출력하는 함수
|
|
bool checkShaderCompilation(GLuint shader, const char* shaderName) {
|
|
GLint status = 0;
|
|
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
|
|
if (status == GL_FALSE) {
|
|
GLint logLength = 0;
|
|
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength);
|
|
std::vector<char> errorLog(logLength);
|
|
glGetShaderInfoLog(shader, logLength, &logLength, &errorLog[0]);
|
|
fprintf(stderr, "셰이더 컴파일 에러 (%s): %s\n", shaderName, &errorLog[0]);
|
|
return false;
|
|
} else {
|
|
//printf("셰이더 컴파일 성공: %s\n", shaderName);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// 셰이더 프로그램 링크 성공 여부를 확인하고 에러 로그를 출력하는 함수
|
|
bool checkProgramLinking(GLuint program, const char* programName) {
|
|
GLint status = 0;
|
|
glGetProgramiv(program, GL_LINK_STATUS, &status);
|
|
if (status == GL_FALSE) {
|
|
GLint logLength = 0;
|
|
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logLength);
|
|
std::vector<char> errorLog(logLength);
|
|
glGetProgramInfoLog(program, logLength, &logLength, &errorLog[0]);
|
|
fprintf(stderr, "셰이더 프로그램 링크 에러 (%s): %s\n", programName, &errorLog[0]);
|
|
return false;
|
|
} else {
|
|
//printf("셰이더 프로그램 링크 성공: %s\n", programName);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void updateShader(EventHandler* eventHandler)
|
|
{
|
|
Camera& camera = eventHandler->camera();
|
|
|
|
//printf("updateShader: g_nColorType=%d\n", g_nColorType);
|
|
|
|
|
|
if(g_nColorType==0)
|
|
{
|
|
if(g_nBitsAllocated>8)
|
|
{
|
|
glUseProgram(g_shaderProgramGray16);
|
|
glUniform2fv(g_shaderPanGray16, 1, camera.pan());
|
|
glUniform1f(g_shaderZoomGray16, camera.zoom());
|
|
glUniform1f(g_shaderAspectGray16, camera.aspect());
|
|
|
|
}
|
|
else if(g_nBitsAllocated==8)
|
|
{
|
|
if(g_nPaletteEntries>0)
|
|
{
|
|
glUseProgram(g_shaderProgramGray8Palette);
|
|
// 팔레트 모드 사용 여부를 유니폼 변수로 전달
|
|
GLint usePaletteLocation = glGetUniformLocation(g_shaderProgramGray8Palette, "u_usePalette");
|
|
glUniform1i(usePaletteLocation, 1); // 팔레트 모드 활성화
|
|
glUniform2fv(g_shaderPanGray8Palette, 1, camera.pan());
|
|
glUniform1f(g_shaderZoomGray8Palette, camera.zoom());
|
|
glUniform1f(g_shaderAspectGray8Palette, camera.aspect());
|
|
|
|
}
|
|
else
|
|
{
|
|
glUseProgram(g_shaderProgramGray8);
|
|
// 팔레트 모드 비활성화
|
|
glUniform2fv(g_shaderPanGray8, 1, camera.pan());
|
|
glUniform1f(g_shaderZoomGray8, camera.zoom());
|
|
glUniform1f(g_shaderAspectGray8, camera.aspect());
|
|
|
|
}
|
|
}
|
|
}
|
|
else if(g_nColorType==1)
|
|
{
|
|
glUseProgram(g_shaderProgramRGB);
|
|
|
|
glUniform2fv(g_shaderPanRGB, 1, camera.pan());
|
|
glUniform1f(g_shaderZoomRGB, camera.zoom());
|
|
glUniform1f(g_shaderAspectRGB, camera.aspect());
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void updateTextureGray8(int nWidth, int nHeight, void* pData)
|
|
{
|
|
glActiveTexture(GL_TEXTURE0);
|
|
glBindTexture(GL_TEXTURE_2D, g_nTextureGray8ID); // 8비트 인덱스 데이터 텍스처
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
|
|
PrintError();
|
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, nWidth, nHeight, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, pData);
|
|
PrintError();
|
|
|
|
//glBindTexture(GL_TEXTURE_2D, 0); // 텍스처 바인딩 해제
|
|
|
|
GLint nTextureID = glGetUniformLocation(g_shaderProgramGray8, "texSampler");
|
|
glUniform1i(nTextureID, 0);
|
|
}
|
|
|
|
|
|
//Uint8* g_pBufTest = NULL;
|
|
void updateTextureGray8Palette(int nWidth, int nHeight, void* pData)
|
|
{
|
|
//updateTextureGray8(nWidth, nHeight, pData);
|
|
//return;
|
|
|
|
glActiveTexture(GL_TEXTURE0);
|
|
glBindTexture(GL_TEXTURE_2D, g_nTextureGray8PaletteID); // 8비트 인덱스 데이터 텍스처
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
|
|
PrintError();
|
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, nWidth, nHeight, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, pData);
|
|
PrintError();
|
|
|
|
//glBindTexture(GL_TEXTURE_2D, 0); // 텍스처 바인딩 해제
|
|
|
|
GLint nTextureID = glGetUniformLocation(g_shaderProgramGray8, "texSampler");
|
|
glUniform1i(nTextureID, 0);
|
|
|
|
|
|
GLint usePaletteLocation = glGetUniformLocation(g_shaderProgramGray8Palette, "u_usePalette");
|
|
//glUniform1i(usePaletteLocation, 0); // 팔레트 모드 활성화
|
|
|
|
glActiveTexture(GL_TEXTURE1);
|
|
glBindTexture(GL_TEXTURE_2D, g_nPaletteTextureID);
|
|
//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
|
|
PrintError();
|
|
//glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, g_nPaletteEntries, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, g_paletteBuffer.data());
|
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, g_nPaletteEntries, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, g_paletteBuffer);
|
|
|
|
PrintError();
|
|
|
|
GLint nPaletteTextureID = glGetUniformLocation(g_shaderProgramGray8Palette, "u_paletteTexture");
|
|
glUniform1i(nPaletteTextureID, 1);
|
|
|
|
//usePaletteLocation = glGetUniformLocation(g_shaderProgramGray8Palette, "u_usePalette");
|
|
glUniform1i(usePaletteLocation, 1); // 팔레트 모드 활성화
|
|
|
|
|
|
}
|
|
|
|
void updateTextureGray16(int nWidth, int nHeight, void* pData)
|
|
{
|
|
glActiveTexture(GL_TEXTURE0);
|
|
glBindTexture(GL_TEXTURE_2D, g_nTextureGray16ID); // 16비트 인덱스 데이터 텍스처
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
|
|
PrintError();
|
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, nWidth, nHeight, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, pData);
|
|
// ✨ 수정된 부분 (WebGL 2)
|
|
// 내부 포맷: GL_R16UI (16비트 부호없는 정수, RED 채널)
|
|
// 외부 포맷: GL_RED_INTEGER (정수형 RED 채널 데이터)
|
|
// 타입: GL_UNSIGNED_SHORT (16비트)
|
|
//glTexImage2D(GL_TEXTURE_2D, 0, GL_R16UI, nWidth, nHeight, 0, GL_RED_INTEGER, GL_UNSIGNED_SHORT, pData);
|
|
PrintError();
|
|
|
|
GLint nTextureID = glGetUniformLocation(g_shaderProgramGray16, "texSampler");
|
|
glUniform1i(nTextureID, 0);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
void updateTextureRGB(int nWidth, int nHeight, void* pData)
|
|
{
|
|
glActiveTexture(GL_TEXTURE0);
|
|
glBindTexture(GL_TEXTURE_2D, g_nTextureID); // 8비트 인덱스 데이터 텍스처
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
|
|
PrintError();
|
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, nWidth, nHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, pData);
|
|
PrintError();
|
|
|
|
//glBindTexture(GL_TEXTURE_2D, 0); // 텍스처 바인딩 해제
|
|
|
|
GLint nTextureID = glGetUniformLocation(g_shaderProgramRGB, "texSampler");
|
|
glUniform1i(nTextureID, 0);
|
|
|
|
//glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, nWidth, nHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, pData);
|
|
}
|
|
|
|
void updateWindowWidthLevelGray8(int nWidth, int nLevel)
|
|
{
|
|
glUniform1f(g_shaderWindowCenterGray8, (float)nLevel);
|
|
glUniform1f(g_shaderWindowWidthGray8, (float)nWidth);
|
|
}
|
|
|
|
void updateWindowWidthLevelGray8Palette(int nWidth, int nLevel)
|
|
{
|
|
glUniform1f(g_shaderWindowCenterGray8Palette, (float)nLevel);
|
|
glUniform1f(g_shaderWindowWidthGray8Palette, (float)nWidth);
|
|
}
|
|
|
|
|
|
void updateWindowWidthLevelGray16(int nWidth, int nLevel)
|
|
{
|
|
glUniform1f(g_shaderWindowCenterGray16, (float)nLevel);
|
|
glUniform1f(g_shaderWindowWidthGray16, (float)nWidth);
|
|
}
|
|
|
|
void updateWindowWidthLevelRGB(int nWidth, int nLevel)
|
|
{
|
|
glUniform1f(g_shaderWindowCenterRGB, (float)nLevel);
|
|
glUniform1f(g_shaderWindowWidthRGB, (float)nWidth);
|
|
}
|
|
|
|
|
|
void initShaderGray8()
|
|
{
|
|
// Create and compile vertex shader
|
|
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
|
|
glShaderSource(vertexShader, 1, &vertexSourceGray8, NULL);
|
|
glCompileShader(vertexShader);
|
|
checkShaderCompilation(vertexShader, "Gray8 Vertex"); // 에러 확인 추가
|
|
|
|
// Create and compile fragment shader
|
|
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
|
|
glShaderSource(fragmentShader, 1, &fragmentSourceGray8, NULL);
|
|
glCompileShader(fragmentShader);
|
|
checkShaderCompilation(fragmentShader, "Gray8 Fragment"); // 에러 확인 추가
|
|
|
|
// Link vertex and fragment shader into shader program and use it
|
|
g_shaderProgramGray8 = glCreateProgram();
|
|
glAttachShader(g_shaderProgramGray8, vertexShader);
|
|
glAttachShader(g_shaderProgramGray8, fragmentShader);
|
|
glLinkProgram(g_shaderProgramGray8);
|
|
checkProgramLinking(g_shaderProgramGray8, "Gray8 Program"); // 에러 확인 추가
|
|
|
|
|
|
|
|
// Get shader variables and initialize them
|
|
g_shaderPanGray8 = glGetUniformLocation(g_shaderProgramGray8, "pan");
|
|
g_shaderZoomGray8 = glGetUniformLocation(g_shaderProgramGray8, "zoom");
|
|
g_shaderAspectGray8 = glGetUniformLocation(g_shaderProgramGray8, "aspect");
|
|
g_shaderWindowCenterGray8 = glGetUniformLocation(g_shaderProgramGray8, "fWindowCenter");
|
|
g_shaderWindowWidthGray8 = glGetUniformLocation(g_shaderProgramGray8, "fWindowWidth");
|
|
|
|
glUniform1f(g_shaderWindowCenterGray8, 128.0f);
|
|
glUniform1f(g_shaderWindowWidthGray8, 255.0f);
|
|
////printf("pan:%d, zoom:%d, aspect:%d, g_shaderWindowCenterGray8:%d, g_shaderWindowWidthGray8:%d\n", g_shaderPanGray8, g_shaderZoomGray8, g_shaderAspectGray8, g_shaderWindowCenterGray8, g_shaderWindowWidthGray8);
|
|
|
|
|
|
}
|
|
|
|
void initShaderGray8Palette()
|
|
{
|
|
// Create and compile vertex shader
|
|
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
|
|
glShaderSource(vertexShader, 1, &vertexSourceGray8, NULL);
|
|
glCompileShader(vertexShader);
|
|
checkShaderCompilation(vertexShader, "Gray8Palette Vertex"); // 에러 확인 추가
|
|
|
|
// Create and compile fragment shader
|
|
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
|
|
glShaderSource(fragmentShader, 1, &fragmentSourceGray8Palette, NULL);
|
|
glCompileShader(fragmentShader);
|
|
checkShaderCompilation(fragmentShader, "Gray8Palette Fragment"); // 에러 확인 추가
|
|
|
|
// Link vertex and fragment shader into shader program and use it
|
|
g_shaderProgramGray8Palette = glCreateProgram();
|
|
glAttachShader(g_shaderProgramGray8Palette, vertexShader);
|
|
glAttachShader(g_shaderProgramGray8Palette, fragmentShader);
|
|
glLinkProgram(g_shaderProgramGray8Palette);
|
|
checkProgramLinking(g_shaderProgramGray8Palette, "Gray8Palette Program"); // 에러 확인 추가
|
|
|
|
|
|
|
|
// Get shader variables and initialize them
|
|
g_shaderPanGray8Palette = glGetUniformLocation(g_shaderProgramGray8Palette, "pan");
|
|
g_shaderZoomGray8Palette = glGetUniformLocation(g_shaderProgramGray8Palette, "zoom");
|
|
g_shaderAspectGray8Palette = glGetUniformLocation(g_shaderProgramGray8Palette, "aspect");
|
|
g_shaderWindowCenterGray8Palette = glGetUniformLocation(g_shaderProgramGray8Palette, "fWindowCenter");
|
|
g_shaderWindowWidthGray8Palette = glGetUniformLocation(g_shaderProgramGray8Palette, "fWindowWidth");
|
|
|
|
// 팔레트 텍스처 유니폼 변수
|
|
g_nPaletteTexLocationIndex = glGetUniformLocation(g_shaderProgramGray8Palette, "u_paletteTexture");
|
|
g_nUsePaletteIndex = glGetUniformLocation(g_shaderProgramGray8Palette, "u_usePalette");
|
|
|
|
glUniform1f(g_shaderWindowCenterGray8Palette, 128.0f);
|
|
glUniform1f(g_shaderWindowWidthGray8Palette, 255.0f);
|
|
////printf("pan:%d, zoom:%d, aspect:%d, g_shaderWindowCenterGray8:%d, g_shaderWindowWidthGray8:%d\n", g_shaderPanGray8Palette, g_shaderZoomGray8Palette, g_shaderAspectGray8Palette, g_shaderWindowCenterGray8Palette, g_shaderWindowWidthGray8Palette);
|
|
|
|
|
|
}
|
|
|
|
void initShaderGray16()
|
|
{
|
|
// Create and compile vertex shader
|
|
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
|
|
glShaderSource(vertexShader, 1, &vertexSourceGray16, NULL);
|
|
glCompileShader(vertexShader);
|
|
checkShaderCompilation(vertexShader, "Gray16 Vertex"); // 에러 확인 추가
|
|
|
|
// Create and compile fragment shader
|
|
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
|
|
glShaderSource(fragmentShader, 1, &fragmentSourceGray16, NULL);
|
|
glCompileShader(fragmentShader);
|
|
checkShaderCompilation(fragmentShader, "Gray16 Fragment"); // 에러 확인 추가
|
|
|
|
// Link vertex and fragment shader into shader program and use it
|
|
g_shaderProgramGray16 = glCreateProgram();
|
|
glAttachShader(g_shaderProgramGray16, vertexShader);
|
|
glAttachShader(g_shaderProgramGray16, fragmentShader);
|
|
glLinkProgram(g_shaderProgramGray16);
|
|
checkProgramLinking(g_shaderProgramGray16, "Gray16 Program"); // 에러 확인 추가
|
|
|
|
|
|
|
|
// Get shader variables and initialize them
|
|
g_shaderPanGray16 = glGetUniformLocation(g_shaderProgramGray16, "pan");
|
|
g_shaderZoomGray16 = glGetUniformLocation(g_shaderProgramGray16, "zoom");
|
|
g_shaderAspectGray16 = glGetUniformLocation(g_shaderProgramGray16, "aspect");
|
|
g_shaderWindowCenterGray16 = glGetUniformLocation(g_shaderProgramGray16, "fWindowCenter");
|
|
g_shaderWindowWidthGray16 = glGetUniformLocation(g_shaderProgramGray16, "fWindowWidth");
|
|
|
|
glUniform1f(g_shaderWindowCenterGray16, 128.0f);
|
|
glUniform1f(g_shaderWindowWidthGray16, 255.0f);
|
|
////printf("pan:%d, zoom:%d, aspect:%d, g_shaderWindowCenterGray16:%d, g_shaderWindowWidthGray16:%d\n", g_shaderPanGray16, g_shaderZoomGray16, g_shaderAspectGray16, g_shaderWindowCenterGray16, g_shaderWindowWidthGray16);
|
|
|
|
|
|
}
|
|
|
|
|
|
void initShaderRGB()
|
|
{
|
|
// Create and compile vertex shader
|
|
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
|
|
glShaderSource(vertexShader, 1, &vertexSourceRGB, NULL);
|
|
glCompileShader(vertexShader);
|
|
checkShaderCompilation(vertexShader, "RGB Vertex"); // 에러 확인 추가
|
|
|
|
// Create and compile fragment shader
|
|
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
|
|
glShaderSource(fragmentShader, 1, &fragmentSourceRGB, NULL);
|
|
glCompileShader(fragmentShader);
|
|
checkShaderCompilation(fragmentShader, "RGB Fragment"); // 에러 확인 추가
|
|
|
|
// Link vertex and fragment shader into shader program and use it
|
|
g_shaderProgramRGB = glCreateProgram();
|
|
////printf("g_shaderProgramRGB: %d\n", g_shaderProgramRGB);
|
|
glAttachShader(g_shaderProgramRGB, vertexShader);
|
|
glAttachShader(g_shaderProgramRGB, fragmentShader);
|
|
glLinkProgram(g_shaderProgramRGB);
|
|
checkProgramLinking(g_shaderProgramRGB, "RGB Program"); // 에러 확인 추가
|
|
//glUseProgram(g_shaderProgramRGB);
|
|
|
|
// Get shader variables and initialize them
|
|
g_shaderPanRGB = glGetUniformLocation(g_shaderProgramRGB, "panRGB");
|
|
g_shaderZoomRGB = glGetUniformLocation(g_shaderProgramRGB, "zoomRGB");
|
|
g_shaderAspectRGB = glGetUniformLocation(g_shaderProgramRGB, "aspectRGB");
|
|
g_shaderWindowCenterRGB = glGetUniformLocation(g_shaderProgramRGB, "fWindowCenterRGB");
|
|
g_shaderWindowWidthRGB = glGetUniformLocation(g_shaderProgramRGB, "fWindowWidthRGB");
|
|
|
|
glUniform1f(g_shaderWindowCenterRGB, 128.0f);
|
|
glUniform1f(g_shaderWindowWidthRGB, 255.0f);
|
|
////printf("pan:%d, zoom:%d, aspect:%d, g_shaderWindowCenterRGB:%d, g_shaderWindowWidthRGB:%d\n", g_shaderPanRGB, g_shaderZoomRGB, g_shaderAspectRGB, g_shaderWindowCenterRGB, g_shaderWindowWidthRGB);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SetEventHandler(EventHandler* eventHandler)
|
|
{
|
|
initShaderGray8Palette();
|
|
|
|
initShaderGray8();
|
|
initShaderGray16();
|
|
initShaderRGB();
|
|
|
|
|
|
g_pEventHandler = eventHandler;
|
|
}
|
|
|
|
void initGeometry(GLuint shaderProgram)
|
|
{
|
|
// Create vertex buffer object and copy vertex data into it
|
|
|
|
printf("initGeometry g_vbo: %d, %d\n", g_vbo, shaderProgram);
|
|
|
|
g_nWindowSizeWidth = (int)g_pEventHandler->camera().windowSize().width;
|
|
g_nWindowSizeHeight = (int)g_pEventHandler->camera().windowSize().height;
|
|
|
|
|
|
float fWindowWidth = (float)g_pEventHandler->camera().windowSize().width;
|
|
float fWindowHeight = (float)g_pEventHandler->camera().windowSize().height;
|
|
|
|
float fWindowRatio = fWindowWidth / fWindowHeight;
|
|
float fImageRatio = (float)g_nFrameTextureWidth / (float)g_nFrameTextureHeight;
|
|
|
|
float fRatioX = 1.0f;
|
|
float fRatioY = 1.0f;
|
|
|
|
|
|
float fRefWidthRatio = (float)g_nFrameTextureWidth / fWindowWidth;
|
|
float fRefHeightRatio = (float)g_nFrameTextureHeight / fWindowHeight;
|
|
|
|
printf("initGeometry Texture(%d, %d), Window(%f, %f)\n", g_nFrameTextureWidth, g_nFrameTextureHeight,
|
|
fWindowWidth, fWindowHeight);
|
|
|
|
|
|
if(fWindowRatio > fImageRatio)
|
|
{
|
|
fRatioY = 1.0f;
|
|
fRatioX = fImageRatio / fWindowRatio;
|
|
}
|
|
else
|
|
{
|
|
fRatioX = 1.0f;
|
|
fRatioY = fWindowRatio / fImageRatio;
|
|
}
|
|
|
|
if(g_vbo==0)
|
|
{
|
|
glGenBuffers(1, &g_vbo);
|
|
printf("glGenBuffer: %d\n", g_vbo);
|
|
|
|
}
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, g_vbo);
|
|
GLfloat vertices[] =
|
|
{
|
|
-1.0f*fRatioX, -1.0f*fRatioY, 0.0f, 0.0f, 1.0f,
|
|
1.0f*fRatioX, -1.0f*fRatioY, 0.0f, 1.0f, 1.0f,
|
|
-1.0f*fRatioX, 1.0f*fRatioY, 0.0f, 0.0f, 0.0f,
|
|
|
|
1.0f*fRatioX, 1.0f*fRatioY, 0.0f, 1.0f, 0.0f,
|
|
-1.0f*fRatioX, 1.0f*fRatioY, 0.0f, 0.0f, 0.0f,
|
|
1.0f*fRatioX, -1.0f*fRatioY, 0.0f, 1.0f, 1.0f,
|
|
};
|
|
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
|
|
|
|
// Specify the layout of the shader vertex data (positions only, 3 floats)
|
|
GLint posAttrib = glGetAttribLocation(shaderProgram, "position");
|
|
glEnableVertexAttribArray(posAttrib);
|
|
// glVertexAttribPointer(posAttrib, 3, GL_FLOAT, GL_FALSE, 0, 0);
|
|
glVertexAttribPointer(posAttrib, 3, GL_FLOAT, GL_FALSE, 5*sizeof(GLfloat), (GLvoid*)0);
|
|
// glEnableVertexAttribArray(0);
|
|
|
|
GLint texAttrib = glGetAttribLocation(shaderProgram, "a_texCoord");
|
|
glEnableVertexAttribArray(texAttrib);
|
|
glVertexAttribPointer(texAttrib, 2, GL_FLOAT, GL_FALSE, 5*sizeof(GLfloat), (GLvoid*)(3*sizeof(GLfloat)));
|
|
//glEnableVertexAttribArray(2);
|
|
|
|
//glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
|
|
////printf("initGeometry(%d, %d)\n", posAttrib, texAttrib);
|
|
if(callback_UpdateWasmInitGeometryComplete!=NULL)
|
|
{
|
|
strRet.clear();
|
|
strRet = "complete initGeometry";
|
|
callback_UpdateWasmInitGeometryComplete((const char*)strRet.c_str());
|
|
}
|
|
}
|
|
|
|
/*
|
|
void initGeometry(GLuint shaderProgram)
|
|
{
|
|
// Create vertex buffer object and copy vertex data into it
|
|
|
|
printf("initGeometry g_vbo: %d, %d\n", g_vbo, shaderProgram);
|
|
|
|
g_nWindowSizeWidth = (int)g_pEventHandler->camera().windowSize().width;
|
|
g_nWindowSizeHeight = (int)g_pEventHandler->camera().windowSize().height;
|
|
|
|
|
|
float fWindowWidth = (float)g_pEventHandler->camera().windowSize().width;
|
|
float fWindowHeight = (float)g_pEventHandler->camera().windowSize().height;
|
|
|
|
float fWindowRatio = fWindowWidth / fWindowHeight;
|
|
|
|
float fFrameTextureRatio = (float)g_nFrameTextureWidth / (float)g_nFrameTextureHeight;
|
|
|
|
float fRatioX = 1.0f;
|
|
float fRatioY = 1.0f;
|
|
|
|
|
|
float fRefWidthRatio = (float)g_nFrameTextureWidth / fWindowWidth;
|
|
float fRefHeightRatio = (float)g_nFrameTextureHeight / fWindowHeight;
|
|
|
|
printf("initGeometry Texture(%d, %d), Window(%f, %f)\n", g_nFrameTextureWidth, g_nFrameTextureHeight,
|
|
fWindowWidth, fWindowHeight);
|
|
|
|
|
|
if(fRefWidthRatio>fRefHeightRatio)
|
|
{
|
|
fRatioX = 1.0f;
|
|
fRatioY = (fWindowWidth*((float)g_nFrameTextureHeight/(float)g_nFrameTextureWidth)) / fWindowHeight;
|
|
}
|
|
else
|
|
{
|
|
fRatioY = 1.0f;
|
|
fRatioX = (fWindowHeight*((float)g_nFrameTextureWidth/(float)g_nFrameTextureHeight)) / fWindowWidth;
|
|
}
|
|
|
|
if(g_vbo==0)
|
|
{
|
|
glGenBuffers(1, &g_vbo);
|
|
printf("glGenBuffer: %d\n", g_vbo);
|
|
|
|
}
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, g_vbo);
|
|
GLfloat vertices[] =
|
|
{
|
|
-1.0f*fRatioX, -1.0f*fRatioY, 0.0f, 0.0f, 1.0f,
|
|
1.0f*fRatioX, -1.0f*fRatioY, 0.0f, 1.0f, 1.0f,
|
|
-1.0f*fRatioX, 1.0f*fRatioY, 0.0f, 0.0f, 0.0f,
|
|
|
|
1.0f*fRatioX, 1.0f*fRatioY, 0.0f, 1.0f, 0.0f,
|
|
-1.0f*fRatioX, 1.0f*fRatioY, 0.0f, 0.0f, 0.0f,
|
|
1.0f*fRatioX, -1.0f*fRatioY, 0.0f, 1.0f, 1.0f,
|
|
};
|
|
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
|
|
|
|
// Specify the layout of the shader vertex data (positions only, 3 floats)
|
|
GLint posAttrib = glGetAttribLocation(shaderProgram, "position");
|
|
glEnableVertexAttribArray(posAttrib);
|
|
// glVertexAttribPointer(posAttrib, 3, GL_FLOAT, GL_FALSE, 0, 0);
|
|
glVertexAttribPointer(posAttrib, 3, GL_FLOAT, GL_FALSE, 5*sizeof(GLfloat), (GLvoid*)0);
|
|
// glEnableVertexAttribArray(0);
|
|
|
|
GLint texAttrib = glGetAttribLocation(shaderProgram, "a_texCoord");
|
|
glEnableVertexAttribArray(texAttrib);
|
|
glVertexAttribPointer(texAttrib, 2, GL_FLOAT, GL_FALSE, 5*sizeof(GLfloat), (GLvoid*)(3*sizeof(GLfloat)));
|
|
//glEnableVertexAttribArray(2);
|
|
|
|
//glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
|
|
////printf("initGeometry(%d, %d)\n", posAttrib, texAttrib);
|
|
}
|
|
*/
|
|
|
|
|
|
void onErrorData(void* arg)
|
|
{
|
|
//printf("onErrorData %d\n", (int)arg);
|
|
}
|
|
|
|
int onLoadedDataInternal(void* arg, void* buffer, int nSize)
|
|
{
|
|
printf("onLoadedDataInternal %p, %d\n", buffer, nSize);
|
|
|
|
//ClearValue();
|
|
//g_paletteBuffer.clear();
|
|
if(g_paletteBuffer!=NULL)
|
|
{
|
|
delete[] g_paletteBuffer;
|
|
g_paletteBuffer = NULL;
|
|
}
|
|
g_nPaletteEntries = 0;
|
|
|
|
|
|
|
|
OFCondition error;
|
|
E_TransferSyntax opt_ixfer = EXS_Unknown;
|
|
E_FileReadMode opt_readMode = ERM_autoDetect;
|
|
Uint8* pData = (Uint8*)buffer;
|
|
|
|
g_nCurrentFrame = 0;
|
|
|
|
int nFindIndex = 0;
|
|
bool bFind = false;
|
|
int i=0;
|
|
Uint8* pDataTmp = (Uint8*)buffer;
|
|
for(i=0 ; i<nSize-4 && bFind==false ; i++)
|
|
{
|
|
if(pDataTmp[i]=='D' && pDataTmp[i+1]=='I' && pDataTmp[i+2]=='C' && pDataTmp[i+3]=='M')
|
|
{
|
|
nFindIndex = i;
|
|
bFind = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(bFind==false)
|
|
{
|
|
if(callback_UpdateDcmImageComplete!=NULL)
|
|
{
|
|
printf("c++ callback_UpdateDcmImageComplete\n");
|
|
callback_UpdateDcmImageComplete(-1);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
//memset(pData, 0x00, nFindIndex-1);
|
|
|
|
g_fFrameTime = 0;
|
|
g_fFrameDelay = 0;
|
|
|
|
// //printf("nFindIndex = 0x%x", nFindIndex);
|
|
|
|
if(g_pDcmDataset!=NULL)
|
|
{
|
|
delete g_pDcmDataset;
|
|
}
|
|
|
|
|
|
DcmInputBufferStream dcmBuffer;
|
|
dcmBuffer.setBuffer(pData, nSize);
|
|
dcmBuffer.setEos();
|
|
|
|
DcmFileFormat dcmff;
|
|
|
|
error = dcmff.read(dcmBuffer);
|
|
if(error.bad())
|
|
{
|
|
printf("Error reading DICOM file: %s\n", error.text());
|
|
if(callback_UpdateDcmImageComplete!=NULL)
|
|
{
|
|
printf("c++ callback_UpdateDcmImageComplete\n");
|
|
callback_UpdateDcmImageComplete(-2);
|
|
}
|
|
return -2;
|
|
}
|
|
|
|
error = dcmff.loadAllDataIntoMemory();
|
|
|
|
g_pDcmDataset = dcmff.getAndRemoveDataset();
|
|
|
|
|
|
OFString strTransferSyntaxUID;
|
|
|
|
|
|
error = dcmff.getMetaInfo()->findAndGetOFString(DCM_TransferSyntaxUID, strTransferSyntaxUID);
|
|
|
|
DcmXfer dcmXfer(strTransferSyntaxUID.c_str());
|
|
|
|
|
|
|
|
g_pDcmDataset->transferInit();
|
|
|
|
//error = g_pDcmDataset->read(dcmBuffer, EXS_JPEGProcess14SV1);
|
|
error = g_pDcmDataset->read(dcmBuffer, dcmXfer.getXfer());
|
|
if(error.bad())
|
|
{
|
|
printf("Dataset State: %u(%s)\n", g_pDcmDataset->transferState(), error.text());
|
|
if(callback_UpdateDcmImageComplete!=NULL)
|
|
{
|
|
printf("c++ callback_UpdateDcmImageComplete\n");
|
|
callback_UpdateDcmImageComplete(-3);
|
|
}
|
|
return -3;
|
|
|
|
}
|
|
|
|
g_pDcmDataset->transferEnd();
|
|
|
|
dcmBuffer.releaseBuffer();
|
|
|
|
|
|
g_nTotalFrames = 1;
|
|
g_nFrameTextureWidth = 0;
|
|
g_nFrameTextureHeight = 0;
|
|
g_nSamplesPerPixel = 1;
|
|
g_nBitsAllocated = 8;
|
|
|
|
|
|
// Photometric Interpretation 태그를 읽어 이미지 종류를 확인합니다.
|
|
OFString photometricInterpretation;
|
|
g_pDcmDataset->findAndGetOFString(DCM_PhotometricInterpretation, photometricInterpretation);
|
|
g_photometricInterpretation = photometricInterpretation.c_str();
|
|
|
|
|
|
Uint16 val = 0;
|
|
g_pDcmDataset->findAndGetUint16(DCM_Rows, val);
|
|
g_nFrameTextureHeight = val;
|
|
|
|
g_pDcmDataset->findAndGetUint16(DCM_Columns, val);
|
|
g_nFrameTextureWidth = val;
|
|
|
|
g_pDcmDataset->findAndGetUint16(DCM_SamplesPerPixel, val);
|
|
g_nSamplesPerPixel = val;
|
|
|
|
g_pDcmDataset->findAndGetUint16(DCM_BitsAllocated, val);
|
|
g_nBitsAllocated = val;
|
|
|
|
size_t bytesPerSample = g_nBitsAllocated / 8;
|
|
if(g_nBitsAllocated%8>0)
|
|
{
|
|
bytesPerSample = bytesPerSample+1;
|
|
}
|
|
|
|
g_nFrameSizeUncompressed = g_nFrameTextureWidth * g_nFrameTextureHeight * g_nSamplesPerPixel * bytesPerSample;
|
|
//g_pBuf = new char[g_nFrameSizeUncompressed];
|
|
|
|
int nMaxValue = pow(2, g_nBitsAllocated) - 1;
|
|
int nMinValue = 0;
|
|
|
|
int nDefaultWindowWidth = nMaxValue - nMinValue;
|
|
int nDefaultWindowCenter = (nMaxValue + nMinValue)/2;
|
|
|
|
Sint32 nFrames = 0;
|
|
OFString strTmp;
|
|
OFString strFrames;
|
|
// g_pDcmDataset->findAndGetSint32(DCM_NumberOfFrames, nFrames);
|
|
g_pDcmDataset->findAndGetOFString(DCM_NumberOfFrames, strFrames);
|
|
|
|
|
|
//usleep(1000*1000);
|
|
|
|
nFrames = atoi(strFrames.c_str());
|
|
if(nFrames<=0)
|
|
{
|
|
nFrames = 1;
|
|
}
|
|
else
|
|
{
|
|
strTmp = "";
|
|
g_pDcmDataset->findAndGetOFString(DCM_FrameTime, strTmp);
|
|
printf("DCM_Frametime: %s\n", strTmp.c_str());
|
|
|
|
g_fFrameTime = atof(strTmp.c_str());
|
|
|
|
strTmp = "";
|
|
g_pDcmDataset->findAndGetOFString(DCM_FrameDelay, strTmp);
|
|
printf("DCM_FrameDelay: %s\n", strTmp.c_str());
|
|
|
|
g_fFrameDelay = atof(strTmp.c_str());
|
|
}
|
|
g_nTotalFrames = nFrames;
|
|
|
|
|
|
|
|
|
|
strTmp = "";
|
|
g_pDcmDataset->findAndGetOFString(DCM_WindowCenter, strTmp);
|
|
g_nDefaultWindowCenter = atoi(strTmp.c_str());
|
|
if(g_nDefaultWindowCenter==0)
|
|
{
|
|
g_nDefaultWindowCenter = nDefaultWindowCenter;
|
|
}
|
|
if(g_nWindowCenter<=0 && g_nDefaultWindowCenter)
|
|
{
|
|
g_nWindowCenter = g_nDefaultWindowCenter;
|
|
}
|
|
|
|
|
|
|
|
strTmp = "";
|
|
g_pDcmDataset->findAndGetOFString(DCM_WindowWidth, strTmp);
|
|
g_nDefaultWindowWidth = atoi(strTmp.c_str());
|
|
if(g_nDefaultWindowWidth==0)
|
|
{
|
|
g_nDefaultWindowWidth = nDefaultWindowWidth;
|
|
}
|
|
if(g_nWindowWidth<=0 && g_nDefaultWindowWidth>0)
|
|
{
|
|
g_nWindowWidth = g_nDefaultWindowWidth;
|
|
}
|
|
|
|
g_nPrevWindowCenter = g_nWindowCenter;
|
|
g_nPrevWindowWidth = g_nWindowWidth;
|
|
|
|
|
|
|
|
if(g_nSamplesPerPixel==3 && g_nBitsAllocated==8)
|
|
{
|
|
g_nColorType = 1;
|
|
}
|
|
else
|
|
{
|
|
g_nColorType = 0;
|
|
}
|
|
|
|
if(g_nFrameTextureWidth==0)
|
|
{
|
|
DcmElement* pElement = NULL;
|
|
g_pDcmDataset->findAndGetElement(DCM_Columns, pElement);
|
|
//printf("pElement: %x\n", pElement);
|
|
}
|
|
|
|
// DcmElement* pixelDataElement = NULL;
|
|
// error = g_pDcmDataset->findAndGetElement(DCM_PixelData, pixelDataElement);
|
|
|
|
g_pixelDataElement = NULL;
|
|
g_pDcmDataset->findAndGetElement(DCM_PixelData, g_pixelDataElement);
|
|
|
|
if(g_cachedFrames!=NULL)
|
|
{
|
|
delete[] g_cachedFrames;
|
|
g_cachedFrames = NULL;
|
|
}
|
|
|
|
|
|
|
|
if(g_pixelDataElement!=NULL)
|
|
{
|
|
OFCondition ofTest;
|
|
OFString decompressedColorModel;
|
|
|
|
Uint32 nDisplayFrameIndex = g_nCurrentFrame;
|
|
|
|
//g_pBuf = (char*)malloc(g_nFrameSizeUncompressed);
|
|
Uint32 startFragment = 0;
|
|
//nDisplayFrameIndex = 1;
|
|
|
|
//g_cachedFrames.clear();
|
|
//g_cachedFrames.resize(g_nTotalFrames);
|
|
g_cachedFrames = (uint8_t*)new uint8_t[g_nFrameSizeUncompressed*g_nTotalFrames];
|
|
memset(g_cachedFrames, 0, g_nFrameSizeUncompressed*g_nTotalFrames);
|
|
|
|
int i=0;
|
|
for(i=0 ; i<g_nTotalFrames ; i++)
|
|
{
|
|
//g_cachedFrames[i].resize(g_nFrameSizeUncompressed);
|
|
//ofTest = g_pixelDataElement->getUncompressedFrame(g_pDcmDataset, i, startFragment, g_cachedFrames[i].data(), g_nFrameSizeUncompressed, decompressedColorModel);
|
|
uint8_t* pTmpData = (uint8_t*)&g_cachedFrames[i*g_nFrameSizeUncompressed];
|
|
ofTest = g_pixelDataElement->getUncompressedFrame(g_pDcmDataset, i, startFragment, pTmpData, g_nFrameSizeUncompressed, decompressedColorModel);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
if(g_nBitsAllocated==8 && g_nSamplesPerPixel==3)
|
|
{
|
|
g_nColorType = 1;
|
|
const Uint8* pImageData = NULL;
|
|
error = g_pDcmDataset->chooseRepresentation(EXS_LittleEndianExplicit, NULL);
|
|
if(error.bad())
|
|
{
|
|
//printf("chooseRepresentation: %s\n", error.text());
|
|
}
|
|
else
|
|
{
|
|
g_pDcmDataset->findAndGetUint8Array(DCM_PixelData, pImageData);
|
|
}
|
|
}
|
|
else if(g_nBitsAllocated==16 && g_nSamplesPerPixel==1)
|
|
{
|
|
g_nColorType = 0;
|
|
const Uint16* pImageData16 = NULL;
|
|
error = g_pDcmDataset->chooseRepresentation(EXS_LittleEndianExplicit, NULL);
|
|
if(error.bad())
|
|
{
|
|
//printf("chooseRepresentation: %s\n", error.text());
|
|
}
|
|
else
|
|
{
|
|
g_pDcmDataset->findAndGetUint16Array(DCM_PixelData, pImageData16);
|
|
}
|
|
}
|
|
else if(g_nBitsAllocated==8 && g_nSamplesPerPixel==1)
|
|
{
|
|
g_nColorType = 0;
|
|
const Uint8* pImageData = NULL;
|
|
error = g_pDcmDataset->chooseRepresentation(EXS_LittleEndianExplicit, NULL);
|
|
if(error.bad())
|
|
{
|
|
printf("chooseRepresentation: %s\n", error.text());
|
|
}
|
|
else
|
|
{
|
|
g_pDcmDataset->findAndGetUint8Array(DCM_PixelData, pImageData);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
g_nColorType = 0;
|
|
}
|
|
|
|
if (photometricInterpretation == "PALETTE COLOR")
|
|
{
|
|
std::cout << "형식: 8-bit Palette Color 이미지" << std::endl;
|
|
|
|
// --- 1. 인덱스 데이터 읽기 (8비트 픽셀 데이터) ---
|
|
const Uint8* indexData = NULL;
|
|
unsigned long count = 0;
|
|
g_pDcmDataset->findAndGetUint8Array(DCM_PixelData, indexData, &count);
|
|
if (indexData) {
|
|
// 이 indexData를 GL_LUMINANCE 포맷을 사용하여 첫 번째 텍스처(인덱스 텍스처)로 업로드합니다.
|
|
// setupTexture(indexData, GL_LUMINANCE, GL_UNSIGNED_BYTE);
|
|
}
|
|
|
|
// --- 2. 컬러 팔레트 데이터 읽기 ---
|
|
// DICOM에서 팔레트는 R, G, B 채널별로 따로 저장되어 있습니다.
|
|
const Uint16* redPalette = NULL;
|
|
const Uint16* greenPalette = NULL;
|
|
const Uint16* bluePalette = NULL;
|
|
unsigned long paletteEntries = 0; // 팔레트의 색상 수 (보통 256)
|
|
|
|
g_pDcmDataset->findAndGetUint16Array(DCM_RedPaletteColorLookupTableData, redPalette, &paletteEntries);
|
|
g_pDcmDataset->findAndGetUint16Array(DCM_GreenPaletteColorLookupTableData, greenPalette);
|
|
g_pDcmDataset->findAndGetUint16Array(DCM_BluePaletteColorLookupTableData, bluePalette);
|
|
|
|
if (redPalette && greenPalette && bluePalette)
|
|
{
|
|
g_nPaletteEntries = paletteEntries;
|
|
std::cout << "컬러 팔레트 로드 성공. 항목 수: " << paletteEntries << std::endl;
|
|
|
|
// --- 3. 팔레트 텍스처 생성 ---
|
|
// 256개의 RGB 색상값을 담을 버퍼를 생성합니다.
|
|
//g_paletteBuffer.reserve(paletteEntries * 3);
|
|
//g_paletteBuffer.resize(paletteEntries * 3);
|
|
|
|
g_paletteBuffer = new uint8_t[paletteEntries*3];
|
|
|
|
for (unsigned int i = 0; i < paletteEntries; ++i)
|
|
{
|
|
// DICOM 팔레트 데이터는 보통 16비트이지만, 화면 표시는 8비트로 충분하므로 상위 8비트를 사용합니다.
|
|
g_paletteBuffer[i * 3 + 0] = redPalette[i] >> 8; // Red
|
|
g_paletteBuffer[i * 3 + 1] = greenPalette[i] >> 8; // Green
|
|
g_paletteBuffer[i * 3 + 2] = bluePalette[i] >> 8; // Blue
|
|
}
|
|
|
|
// 이 paletteBuffer를 GL_RGB 포맷을 사용하여 두 번째 텍스처(팔레트 텍스처)로 업로드합니다.
|
|
// GLuint paletteTextureID;
|
|
// glGenTextures(1, &paletteTextureID);
|
|
// glBindTexture(GL_TEXTURE_2D, paletteTextureID);
|
|
// glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, g_nPaletteEntries, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, g_paletteBuffer.data());
|
|
// ... (텍스처 파라미터 설정)
|
|
}
|
|
}
|
|
|
|
if(callback_UpdateDcmImageComplete!=NULL)
|
|
{
|
|
printf("c++ callback_UpdateDcmImageComplete\n");
|
|
callback_UpdateDcmImageComplete(1);
|
|
}
|
|
|
|
//ResetWindowWidthLevel();
|
|
g_nWindowWidth = g_nDefaultWindowWidth;
|
|
g_nWindowCenter = g_nDefaultWindowCenter;
|
|
g_nPrevWindowWidth = g_nDefaultWindowWidth;
|
|
g_nPrevWindowCenter = g_nDefaultWindowCenter;
|
|
|
|
return 1;
|
|
//g_bChange = false;
|
|
}
|
|
|
|
int onLoadedData(void* arg, void* buffer, int nSize)
|
|
{
|
|
int nRet = onLoadedDataInternal(arg, buffer, nSize);
|
|
if(nRet)
|
|
{
|
|
g_bChange = true;
|
|
printf("load OK!\n");
|
|
|
|
}
|
|
|
|
free(buffer);
|
|
|
|
return nRet;
|
|
}
|
|
|
|
void onErrorData2(unsigned nHandle, void* arg, int nHTTPCode, const char* strDescription)
|
|
{
|
|
printf("onErrorData2 %d, %p, %d, %s\n", nHandle, arg, nHTTPCode, strDescription);
|
|
}
|
|
|
|
void onLoadedData2(unsigned nHandle, void* arg, void* buffer, unsigned nSize)
|
|
{
|
|
if(g_bCancelLoad==true)
|
|
{
|
|
return;
|
|
}
|
|
|
|
printf("onLoadedData2: %d(%d), %p, %p, %d\n", g_nHandleWget, nHandle, arg, buffer, nSize);
|
|
|
|
if(g_nHandleWget==nHandle)
|
|
{
|
|
onLoadedData(arg, buffer, nSize);
|
|
}
|
|
|
|
|
|
}
|
|
|
|
void onProgressData2(unsigned nHandle, void* arg, int nLoadedBytes, int nTotalBytes)
|
|
{
|
|
//printf("onProgressData2: %d, %d, %d, %d\n", nHandle, (int)arg, nLoadedBytes, nTotalBytes);
|
|
g_nReadBytes = nLoadedBytes;
|
|
g_nTotalBytes = nTotalBytes;
|
|
}
|
|
|
|
void initTextureGray8()
|
|
{
|
|
int w = g_nFrameTextureWidth;
|
|
int h = g_nFrameTextureHeight;
|
|
{
|
|
//glGenTextures(1, &g_nTextureGray8ID);
|
|
|
|
updateTextureGray8(w, h, (void*)&g_cachedFrames[0]);
|
|
}
|
|
}
|
|
|
|
void initTextureGray8Palette()
|
|
{
|
|
int w = g_nFrameTextureWidth;
|
|
int h = g_nFrameTextureHeight;
|
|
{
|
|
//glGenTextures(1, &g_nTextureGray8PaletteID);
|
|
//glGenTextures(1, &g_nPaletteTextureID);
|
|
|
|
updateTextureGray8Palette(w, h, (void*)&g_cachedFrames[0]);
|
|
}
|
|
}
|
|
|
|
|
|
void initTextureGray16()
|
|
{
|
|
int w = g_nFrameTextureWidth;
|
|
int h = g_nFrameTextureHeight;
|
|
|
|
{
|
|
//glGenTextures(1, &g_nTextureGray16ID);
|
|
updateTextureGray16(w, h, (void*)&g_cachedFrames[0]);
|
|
}
|
|
}
|
|
|
|
|
|
void initTextureRGB()
|
|
{
|
|
int w = g_nFrameTextureWidth;
|
|
int h = g_nFrameTextureHeight;
|
|
{
|
|
//glGenTextures(1, &g_nTextureID);
|
|
updateTextureRGB(w, h, (void*)&g_cachedFrames[0]);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#ifdef __EMSCRIPTEN__
|
|
|
|
#ifdef __cplusplus
|
|
#define EXTERN extern "C"
|
|
#else
|
|
#define EXTERN
|
|
#endif
|
|
|
|
/**
|
|
* Svelte의 on:mousemove 이벤트로부터 마우스의 상대적 이동량을 받아 처리합니다.
|
|
* @param deltaX 이전 프레임으로부터의 X축 이동량 (event.movementX)
|
|
* @param deltaY 이전 프레임으로부터의 Y축 이동량 (event.movementY)
|
|
*/
|
|
EXTERN EMSCRIPTEN_KEEPALIVE void DoMouseDown(int x, int y)
|
|
{
|
|
// EventHandler 인스턴스가 유효한지 확인합니다.
|
|
if (!g_pEventHandler) {
|
|
return;
|
|
}
|
|
|
|
g_pEventHandler->SetMouseButtonDownPosition(x, y);
|
|
|
|
printf("DoMouseDown (%d, %d)\n", x, y);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE void DoMouseUp(int x, int y)
|
|
{
|
|
// EventHandler 인스턴스가 유효한지 확인합니다.
|
|
if (!g_pEventHandler) {
|
|
return;
|
|
}
|
|
|
|
g_pEventHandler->SetMouseButtonUpPosition(x, y);
|
|
|
|
printf("DoMouseUp (%d, %d)\n", x, y);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE void DoMouseMove(int x, int y)
|
|
{
|
|
// EventHandler 인스턴스가 유효한지 확인합니다.
|
|
if (!g_pEventHandler) {
|
|
return;
|
|
}
|
|
|
|
g_pEventHandler->panEventMouse(x, y);
|
|
|
|
printf("Pan 모드에서 마우스 이동: (%d, %d)\n", x, y);
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE void DoMouseWheelZoom(float fDelta)
|
|
{
|
|
// EventHandler 인스턴스가 유효한지 확인합니다.
|
|
if (!g_pEventHandler) {
|
|
return;
|
|
}
|
|
|
|
g_pEventHandler->zoom(fDelta);
|
|
|
|
printf("zoom (%f)\n", fDelta);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE void DoPinchZoom(float scale, float pinchX, float pinchY) {
|
|
if (!g_pEventHandler) {
|
|
return;
|
|
}
|
|
|
|
Camera& camera = g_pEventHandler->camera();
|
|
|
|
// 1. 줌을 적용하기 전, 핀치 중심점의 월드(World) 좌표를 계산합니다.
|
|
float preZoomWorldX, preZoomWorldY;
|
|
camera.windowToWorldCoords((int)pinchX, (int)pinchY, preZoomWorldX, preZoomWorldY);
|
|
|
|
// 2. 전달받은 비율(scale)을 사용하여 줌 레벨을 업데이트합니다.
|
|
// camera.cpp에 setZoomDelta가 있으므로, 현재 줌과의 차이를 계산하여 전달합니다.
|
|
float currentZoom = camera.zoom();
|
|
float newZoom = currentZoom * scale;
|
|
float zoomDelta = newZoom - currentZoom;
|
|
camera.setZoomDelta(zoomDelta); // 기존 setZoomDelta 함수를 활용
|
|
|
|
// 3. 줌을 적용한 후, 동일한 화면 좌표가 어떤 월드 좌표를 가리키는지 다시 계산합니다.
|
|
float postZoomWorldX, postZoomWorldY;
|
|
camera.windowToWorldCoords((int)pinchX, (int)pinchY, postZoomWorldX, postZoomWorldY);
|
|
|
|
// 4. 두 월드 좌표의 차이만큼 화면을 이동(pan)시켜 줍니다.
|
|
// 이렇게 하면 핀치 중심점은 줌 전후에 같은 위치에 머무르게 됩니다.
|
|
Vec2 deltaWorld = { postZoomWorldX - preZoomWorldX, postZoomWorldY - preZoomWorldY };
|
|
camera.setPanDelta(deltaWorld); // 기존 setPanDelta 함수를 활용
|
|
}
|
|
|
|
/*
|
|
EXTERN EMSCRIPTEN_KEEPALIVE void DoMouseMove(float deltaX, float deltaY)
|
|
{
|
|
// EventHandler 인스턴스가 유효한지 확인합니다.
|
|
if (!g_pEventHandler) {
|
|
return;
|
|
}
|
|
|
|
// 현재 활성화된 모드에 따라 다른 동작을 수행합니다.
|
|
if (g_currentMode & MODE_PAN) {
|
|
// --- 이동 (Pan) 모드 로직 ---
|
|
|
|
// Camera 객체에 접근합니다.
|
|
Camera& camera = g_pEventHandler->camera();
|
|
|
|
// 전달받은 delta 값을 화면 크기로 정규화하면 너무 민감할 수 있으므로,
|
|
// 줌 레벨을 고려하여 이동량을 계산합니다.
|
|
// 이 계산 방식은 기존 panEventMouse와 유사하게 동작합니다.
|
|
float panDeltaX = deltaX / camera.zoom();
|
|
float panDeltaY = deltaY / camera.zoom();
|
|
|
|
// Camera의 pan 값을 업데이트합니다. Y축은 보통 반전시켜줍니다.
|
|
camera.setPanDelta({ panDeltaX, -panDeltaY });
|
|
|
|
printf("Pan 모드에서 마우스 이동: dX=%.2f, dY=%.2f\n", deltaX, deltaY);
|
|
|
|
|
|
} else if (g_currentMode & MODE_WIDTHLEVEL) {
|
|
// --- Window/Level 모드 로직 ---
|
|
|
|
// 이전에 사용하시던 m_fDeltaX, m_fDeltaY와 유사하게
|
|
// deltaX와 deltaY를 사용하여 Window Width와 Level을 조절하는 로직을 여기에 구현합니다.
|
|
// 예를 들어, g_pEventHandler에 관련 값을 업데이트하는 함수를 만들 수 있습니다.
|
|
|
|
// 예시:
|
|
// g_pEventHandler->UpdateWindowLevel(deltaX, deltaY);
|
|
|
|
printf("Window/Level 모드에서 마우스 이동: dX=%.2f, dY=%.2f\n", deltaX, deltaY);
|
|
}
|
|
}
|
|
*/
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE int CancelLoadImage()
|
|
{
|
|
printf("c++ CancelLoadImage: %d\n", g_nHandleWget);
|
|
g_bCancelLoad = true;
|
|
emscripten_async_wget2_abort(g_nHandleWget);
|
|
|
|
printf("c++ Cancel Complete\n");
|
|
return 0;
|
|
}
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE int UpdateDcmImage(std::string strFile)
|
|
{
|
|
g_nTotalBytes = 1000000000;
|
|
g_nReadBytes = 0;
|
|
|
|
g_bCancelLoad = false;
|
|
|
|
g_bLoadImage = true;
|
|
|
|
//emscripten_async_wget_data(strFile.c_str(), (void*)135, onLoadedData, onErrorData);
|
|
g_nHandleWget = emscripten_async_wget2_data(strFile.c_str(), "GET", "", (void*)135, false, onLoadedData2, onErrorData2, onProgressData2);
|
|
|
|
printf("c++ UpdateDcmImage: %d(%s)\n", g_nHandleWget, strFile.c_str());
|
|
|
|
return 0;
|
|
}
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE int ParseDicomToPixelData(char* buffer, int nDataSize)
|
|
{
|
|
onLoadedDataInternal(NULL, (void*)buffer, nDataSize);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE void Shutdown() {
|
|
printf("--- C++ Shutdown() called. Terminating main loop and cleaning up. ---\n");
|
|
|
|
// 1. Emscripten의 메인 루프를 취소합니다.
|
|
emscripten_cancel_main_loop();
|
|
|
|
// 2. 생성했던 모든 GL 리소스를 삭제합니다.
|
|
if (g_vbo != 0) {
|
|
glDeleteBuffers(1, &g_vbo);
|
|
g_vbo = 0;
|
|
}
|
|
|
|
if (g_nTextureID != 0) {
|
|
glDeleteTextures(1, &g_nTextureID);
|
|
g_nTextureID = 0;
|
|
}
|
|
if (g_nTextureGray8ID != 0) {
|
|
glDeleteTextures(1, &g_nTextureGray8ID);
|
|
g_nTextureGray8ID = 0;
|
|
}
|
|
if (g_nTextureGray16ID != 0) {
|
|
glDeleteTextures(1, &g_nTextureGray16ID);
|
|
g_nTextureGray16ID = 0;
|
|
}
|
|
if (g_nTextureGray8PaletteID != 0) {
|
|
glDeleteTextures(1, &g_nTextureGray8PaletteID);
|
|
g_nTextureGray8PaletteID = 0;
|
|
}
|
|
if (g_nPaletteTextureID != 0) {
|
|
glDeleteTextures(1, &g_nPaletteTextureID);
|
|
g_nPaletteTextureID = 0;
|
|
}
|
|
|
|
|
|
if (g_shaderProgramGray8 != 0) {
|
|
glDeleteProgram(g_shaderProgramGray8);
|
|
g_shaderProgramGray8 = 0;
|
|
}
|
|
|
|
if (g_shaderProgramGray8Palette != 0) {
|
|
glDeleteProgram(g_shaderProgramGray8Palette);
|
|
g_shaderProgramGray8Palette = 0;
|
|
}
|
|
|
|
if (g_shaderProgramGray16 != 0) {
|
|
glDeleteProgram(g_shaderProgramGray16);
|
|
g_shaderProgramGray16 = 0;
|
|
}
|
|
|
|
if (g_shaderProgramRGB != 0) {
|
|
glDeleteProgram(g_shaderProgramRGB);
|
|
g_shaderProgramRGB = 0;
|
|
}
|
|
|
|
if(g_pDcmDataset!=NULL)
|
|
{
|
|
delete g_pDcmDataset;
|
|
g_pDcmDataset = NULL;
|
|
}
|
|
|
|
//g_paletteBuffer.clear();
|
|
if(g_paletteBuffer!=NULL)
|
|
{
|
|
delete[] g_paletteBuffer;
|
|
g_paletteBuffer = NULL;
|
|
}
|
|
|
|
//g_cachedFrames.clear();
|
|
if(g_cachedFrames!=NULL)
|
|
{
|
|
delete[] g_cachedFrames;
|
|
g_cachedFrames = NULL;
|
|
}
|
|
|
|
|
|
// ... 다른 모든 셰이더 프로그램, 텍스처 등도 동일하게 정리합니다. ...
|
|
}
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE int ProcessDCMFile(char* buffer, int nDataSize)
|
|
{
|
|
//ClearValue();
|
|
onLoadedData(NULL, (void*)buffer, nDataSize);
|
|
|
|
return 0;
|
|
}
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE int SetWindowWidthLevel(int nWindowCenter, int nWindowWidth)
|
|
{
|
|
//printf("New nWindowCenter:%d, nWindowWidth:%d\n", nWindowCenter, nWindowWidth);
|
|
|
|
g_nWindowCenter = nWindowCenter;
|
|
g_nWindowWidth = nWindowWidth;
|
|
|
|
g_nPrevWindowCenter = nWindowCenter;
|
|
g_nPrevWindowWidth = nWindowWidth;
|
|
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE int SetDisplayFrameIndex(int nIndex)
|
|
{
|
|
g_nCurrentFrame = nIndex;
|
|
|
|
if(g_nCurrentFrame>=g_nTotalFrames)
|
|
{
|
|
g_nCurrentFrame = g_nTotalFrames-1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE int SetResizeFrame(int nWidth, int nHeight)
|
|
{
|
|
printf("SetResizeFrame: (%d, %d)\n", nWidth, nHeight);
|
|
|
|
//g_pEventHandler->camera().setWindowSize(nWidth, nHeight);
|
|
g_nWindowSizeWidth = nWidth;
|
|
g_nWindowSizeHeight = nHeight;
|
|
return 0;
|
|
}
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE int ResetView()
|
|
{
|
|
g_pEventHandler->camera().reset();
|
|
return 0;
|
|
}
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE int ResetWindowWidthLevel()
|
|
{
|
|
g_nWindowWidth = g_nDefaultWindowWidth;
|
|
g_nWindowCenter = g_nDefaultWindowCenter;
|
|
g_nPrevWindowWidth = g_nDefaultWindowWidth;
|
|
g_nPrevWindowCenter = g_nDefaultWindowCenter;
|
|
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE int GetTotalBytes()
|
|
{
|
|
return g_nTotalBytes;
|
|
}
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE int GetReadBytes()
|
|
{
|
|
return g_nReadBytes;
|
|
}
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE int GetWindowWidth()
|
|
{
|
|
return g_nWindowWidth;
|
|
}
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE int GetWindowCenter()
|
|
{
|
|
return g_nWindowCenter;
|
|
}
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE int GetTotalFrames()
|
|
{
|
|
return g_nTotalFrames;
|
|
}
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE float GetFrameUpdateTimeDelay()
|
|
{
|
|
return (g_fFrameDelay + g_fFrameTime);
|
|
}
|
|
|
|
|
|
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE char* getDumpJson()
|
|
{
|
|
printf("--- GetDumpJson() called ---\n");
|
|
|
|
nlohmann::json j;
|
|
j["WindowWidthLevel"]["WindowWidth"] = g_nPrevWindowWidth;
|
|
j["WindowWidthLevel"]["WindowCenter"] = g_nPrevWindowCenter;
|
|
j["visible"] = g_bVisible;
|
|
j["viewerID"] = g_nViewerID;
|
|
|
|
j["Width"] = g_nWindowSizeWidth;
|
|
j["Height"] = g_nWindowSizeHeight;
|
|
|
|
g_pEventHandler->camera().toJson(j);
|
|
|
|
strRet.clear();
|
|
strRet = j.dump(2);
|
|
|
|
return (char*)strRet.c_str();
|
|
}
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE void setDumpJson(const char* pStrDumpJson)
|
|
{
|
|
printf("--- setDumpJson(%s) called ---\n", pStrDumpJson);
|
|
|
|
g_pEventHandler->camera().fromString(pStrDumpJson);
|
|
|
|
printf("Camera::fromString enter!%s\n", pStrDumpJson);
|
|
try
|
|
{
|
|
nlohmann::json json_obj = nlohmann::json::parse(pStrDumpJson);
|
|
const auto& WidthLevel_data = json_obj["WindowWidthLevel"];
|
|
g_nWindowWidth = WidthLevel_data["WindowWidth"];
|
|
g_nWindowCenter = WidthLevel_data["WindowCenter"];
|
|
g_nPrevWindowWidth = g_nWindowWidth;
|
|
g_nPrevWindowCenter = g_nWindowCenter;
|
|
|
|
if (json_obj.contains("visible")) {
|
|
// 2. 키가 존재하면, 값을 가져와 g_bVisible에 할당합니다.
|
|
g_bVisible = json_obj["visible"];
|
|
printf("Parsed 'visible': %s\n", g_bVisible ? "true" : "false");
|
|
} else {
|
|
// 3. 키가 존재하지 않으면, 기본값을 설정하여 예기치 않은 동작을 방지합니다.
|
|
printf("'visible' key not found in JSON. Using default value (true).\n");
|
|
g_bVisible = true;
|
|
}
|
|
|
|
if(json_obj.contains("viewerID"))
|
|
{
|
|
g_nViewerID = json_obj["viewerID"];
|
|
}
|
|
else
|
|
{
|
|
printf("can not find viewerID\n");
|
|
}
|
|
}
|
|
catch (const nlohmann::json::exception& e)
|
|
{
|
|
// It's good practice to catch potential errors
|
|
printf("JSON parsing error: %s\n", e.what());
|
|
}
|
|
|
|
}
|
|
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE void SetInteractionMode(int mode) {
|
|
if (mode & InteractionMode::MODE_PAN ) {
|
|
printf("C++: Interaction mode set to PAN\n");
|
|
}
|
|
if (mode & InteractionMode::MODE_WIDTHLEVEL ) {
|
|
printf("C++: Interaction mode set to WIDTHLEVEL\n");
|
|
}
|
|
|
|
if (mode & InteractionMode::MODE_ZOOM ) {
|
|
printf("C++: Interaction mode set to ZOOM\n");
|
|
}
|
|
|
|
if (mode & InteractionMode::MODE_SCROLL ) {
|
|
printf("C++: Interaction mode set to SCROLL\n");
|
|
}
|
|
|
|
g_currentMode = mode;
|
|
|
|
g_pEventHandler->SetInteractionMode(g_currentMode);
|
|
}
|
|
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE bool SetCallbackUpdateDcmImageComplete(callback_UpdateDcmImage callback_)
|
|
{
|
|
//printf("SetCallbackUpdateDcmImage: %x\n", callback_);
|
|
|
|
callback_UpdateDcmImageComplete = callback_;
|
|
return true;
|
|
}
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE bool SetCallbackUpdateWasmInitGeometryComplete(callback_UpdateWasmInitGeometry callback_)
|
|
{
|
|
//printf("SetCallbackUpdateDcmImage: %x\n", callback_);
|
|
|
|
callback_UpdateWasmInitGeometryComplete = callback_;
|
|
|
|
return true;
|
|
}
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE int GetFrameWidth()
|
|
{
|
|
return g_nFrameTextureWidth;
|
|
}
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE int GetFrameHeight()
|
|
{
|
|
return g_nFrameTextureHeight;
|
|
}
|
|
|
|
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE int GetFrameSizeUncompressed()
|
|
{
|
|
return g_nFrameSizeUncompressed;
|
|
}
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE int GetPaletteEntries()
|
|
{
|
|
return g_nPaletteEntries;
|
|
}
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE int GetDefaultWindowWidth()
|
|
{
|
|
return g_nDefaultWindowWidth;
|
|
}
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE int GetDefaultWindowCenter()
|
|
{
|
|
return g_nDefaultWindowCenter;
|
|
}
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE int GetBitsAllocated()
|
|
{
|
|
return g_nBitsAllocated;
|
|
}
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE int GetSamplesPerPixel()
|
|
{
|
|
return g_nSamplesPerPixel;
|
|
}
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE int GetColorType()
|
|
{
|
|
return g_nColorType;
|
|
}
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE int GetWidth()
|
|
{
|
|
return g_nWindowSizeWidth;
|
|
}
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE int GetHeight()
|
|
{
|
|
return g_nWindowSizeHeight;
|
|
}
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE float GetFrameTime()
|
|
{
|
|
return g_fFrameTime;
|
|
}
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE float GetFrameDelay()
|
|
{
|
|
return g_fFrameDelay;
|
|
}
|
|
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE uint8_t* GetPixelData()
|
|
{
|
|
printf("--- GetPixelData() called ---\n");
|
|
|
|
return g_cachedFrames;
|
|
}
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE uint8_t* GetPaletteData()
|
|
{
|
|
printf("--- GetPixelData() called ---\n");
|
|
|
|
return g_paletteBuffer;
|
|
}
|
|
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE void SetDicomData(uint8_t* pPixelData, uint8_t* pPaletteData,
|
|
int nFrameWidth, int nFrameHeight, int nBitsAllocated, int nSamplesPerPixel,
|
|
int nDefaultWindowWidth, int nDefaultWindowCenter, int nPaletteEntries, int nColorType,
|
|
int nTotalFrames, float fFrameTime, float fFrameDelay)
|
|
{
|
|
if(g_paletteBuffer!=NULL)
|
|
{
|
|
delete[] g_paletteBuffer;
|
|
g_paletteBuffer = NULL;
|
|
}
|
|
|
|
if(g_cachedFrames!=NULL)
|
|
{
|
|
delete[] g_cachedFrames;
|
|
g_cachedFrames = NULL;
|
|
}
|
|
|
|
g_nDefaultWindowWidth = nDefaultWindowWidth;
|
|
g_nDefaultWindowCenter = nDefaultWindowCenter;
|
|
|
|
g_nWindowWidth = g_nDefaultWindowWidth;
|
|
g_nWindowCenter = g_nDefaultWindowCenter;
|
|
g_nPrevWindowWidth = g_nDefaultWindowWidth;
|
|
g_nPrevWindowCenter = g_nDefaultWindowCenter;
|
|
|
|
|
|
g_nBitsAllocated = nBitsAllocated;
|
|
g_nSamplesPerPixel = nSamplesPerPixel;
|
|
g_nFrameTextureWidth = nFrameWidth;
|
|
g_nFrameTextureHeight = nFrameHeight;
|
|
|
|
|
|
g_nPaletteEntries = nPaletteEntries;
|
|
g_nColorType = nColorType;
|
|
|
|
g_nTotalFrames = nTotalFrames;
|
|
|
|
g_nCurrentFrame = 0;
|
|
|
|
g_fFrameTime = fFrameTime;
|
|
g_fFrameDelay = fFrameDelay;
|
|
|
|
size_t bytesPerSample = nBitsAllocated / 8;
|
|
if(nBitsAllocated%8>0)
|
|
{
|
|
bytesPerSample = bytesPerSample+1;
|
|
}
|
|
|
|
g_nFrameSizeUncompressed = g_nFrameTextureWidth * g_nFrameTextureHeight * g_nSamplesPerPixel * bytesPerSample;
|
|
|
|
|
|
// --- ✨ 3. 데이터 "복사" ---
|
|
// JavaScript가 넘겨준 데이터를 C++ 소유의 새 메모리 공간으로 복사합니다.
|
|
size_t totalPixelDataSize = (size_t)g_nFrameSizeUncompressed * nTotalFrames;
|
|
|
|
|
|
g_cachedFrames = new uint8_t[totalPixelDataSize];
|
|
memcpy(g_cachedFrames, pPixelData, totalPixelDataSize);
|
|
|
|
if (pPaletteData != NULL && nPaletteEntries > 0) {
|
|
size_t paletteDataSize = nPaletteEntries * 3; // 팔레트는 보통 RGB 3바이트
|
|
g_paletteBuffer = new uint8_t[paletteDataSize];
|
|
memcpy(g_paletteBuffer, pPaletteData, paletteDataSize);
|
|
}
|
|
|
|
g_bChange = true;
|
|
|
|
g_bUseFirst = false;
|
|
|
|
printf("SetDicomData c++(int nFrameWidth:%d, int nFrameHeight:%d, int nBitsAllocated:%d, int nSamplesPerPixel:%d, \
|
|
int nDefaultWindowWidth:%d, int nDefaultWindowCenter:%d, int nPaletteEntries:%d, int nColorType:%d \
|
|
int nTotalFrames:%d, float fFrameTime:%f, float fFrameDelay:%f)\n",
|
|
nFrameWidth, nFrameHeight, nBitsAllocated, nSamplesPerPixel,
|
|
nDefaultWindowWidth, nDefaultWindowCenter, nPaletteEntries, nColorType,
|
|
nTotalFrames, fFrameTime, fFrameDelay);
|
|
}
|
|
|
|
EXTERN EMSCRIPTEN_KEEPALIVE void ClearImageData()
|
|
{
|
|
if(g_paletteBuffer!=NULL)
|
|
{
|
|
delete[] g_paletteBuffer;
|
|
g_paletteBuffer = NULL;
|
|
}
|
|
|
|
if(g_cachedFrames!=NULL)
|
|
{
|
|
delete[] g_cachedFrames;
|
|
g_cachedFrames = NULL;
|
|
}
|
|
|
|
g_nDefaultWindowWidth = 0;
|
|
g_nDefaultWindowCenter = 0;
|
|
|
|
g_nWindowWidth = 0;
|
|
g_nWindowCenter = 0;
|
|
g_nPrevWindowWidth = 0;
|
|
g_nPrevWindowCenter = 0;
|
|
|
|
|
|
g_nBitsAllocated = 0;
|
|
g_nSamplesPerPixel = 0;
|
|
g_nFrameTextureWidth = 0;
|
|
g_nFrameTextureHeight = 0;
|
|
|
|
|
|
g_nPaletteEntries = -1;
|
|
g_nColorType = -1;
|
|
|
|
g_nTotalFrames = 0;
|
|
|
|
g_nCurrentFrame = 0;
|
|
|
|
g_fFrameTime = 0;
|
|
g_fFrameDelay = 0;
|
|
g_nFrameSizeUncompressed = 0;
|
|
|
|
g_bChange = true;
|
|
|
|
g_bUseFirst = false;
|
|
}
|
|
|
|
#endif
|
|
|
|
void UpdateFrame()
|
|
{
|
|
OFCondition ofTest;
|
|
OFString decompressedColorModel;
|
|
|
|
Uint32 nDisplayFrameIndex = g_nCurrentFrame;
|
|
|
|
Uint32 startFragment = 0;
|
|
//nDisplayFrameIndex = 1;
|
|
|
|
Uint32 nDisplay = nDisplayFrameIndex/1;
|
|
|
|
//ofTest = g_pixelDataElement->getUncompressedFrame(g_pDcmDataset, nDisplay, startFragment, g_pBuf, g_nFrameSizeUncompressed, decompressedColorModel);
|
|
|
|
if(ofTest.bad())
|
|
{
|
|
//printf("getUncompressedFrame error: %s\n", ofTest.text());
|
|
}
|
|
//printf("nDisplay: %d\n", nDisplay);
|
|
nDisplayFrameIndex++;
|
|
|
|
g_nCurrentFrame = nDisplayFrameIndex;
|
|
|
|
if(g_nCurrentFrame>=g_nTotalFrames*1)
|
|
{
|
|
g_nCurrentFrame = 0;
|
|
}
|
|
|
|
if(g_nColorType==0)
|
|
{
|
|
if(g_nBitsAllocated==8)
|
|
{
|
|
//printf("updateTextureGray8: %d, %d, %d, %d, %s\nerror: %s\n", g_nFrameTextureWidth, g_nFrameTextureHeight, g_nCurrentFrame, g_nFrameSizeUncompressed, decompressedColorModel.c_str(), ofTest.text());
|
|
if(g_nPaletteEntries>0)
|
|
{
|
|
|
|
//printf("updateTextureGray8Palette: %d, %d, %d, %d, %s\nerror: %s\n", g_nFrameTextureWidth, g_nFrameTextureHeight, g_nCurrentFrame, g_nFrameSizeUncompressed, decompressedColorModel.c_str(), ofTest.text());
|
|
|
|
//updateTextureGray8Palette(g_nFrameTextureWidth, g_nFrameTextureHeight, g_cachedFrames[nDisplay].data());
|
|
updateTextureGray8Palette(g_nFrameTextureWidth, g_nFrameTextureHeight, (void*)&g_cachedFrames[nDisplay*g_nFrameSizeUncompressed]);
|
|
}
|
|
else
|
|
{
|
|
//updateTextureGray8(g_nFrameTextureWidth, g_nFrameTextureHeight, g_cachedFrames[nDisplay].data());
|
|
updateTextureGray8(g_nFrameTextureWidth, g_nFrameTextureHeight, (void*)&g_cachedFrames[nDisplay*g_nFrameSizeUncompressed]);
|
|
}
|
|
|
|
}
|
|
else if(g_nBitsAllocated>8)
|
|
{
|
|
//printf("updateTextureGray16: %d, %d, %d, %d, %s\nerror: %s\n", g_nFrameTextureWidth, g_nFrameTextureHeight, g_nCurrentFrame, g_nFrameSizeUncompressed, decompressedColorModel.c_str(), ofTest.text());
|
|
//updateTextureGray16(g_nFrameTextureWidth, g_nFrameTextureHeight, g_cachedFrames[nDisplay].data());
|
|
updateTextureGray16(g_nFrameTextureWidth, g_nFrameTextureHeight, (void*)&g_cachedFrames[nDisplay*g_nFrameSizeUncompressed]);
|
|
}
|
|
|
|
}
|
|
else if(g_nColorType==1)
|
|
{
|
|
|
|
////printf("updateTextureRGB: %d, %d, %d, %d, %s\nerror: %s\n", g_nFrameTextureWidth, g_nFrameTextureHeight, g_nCurrentFrame, g_nFrameSizeUncompressed, decompressedColorModel.c_str(), ofTest.text());
|
|
//updateTextureRGB(g_nFrameTextureWidth, g_nFrameTextureHeight, g_cachedFrames[nDisplay].data());
|
|
updateTextureRGB(g_nFrameTextureWidth, g_nFrameTextureHeight, (void*)&g_cachedFrames[nDisplay*g_nFrameSizeUncompressed]);
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
//[상태 업데이트] → [셰이더 선택] → [셰이더 활성화] → [유니폼 업데이트] → [그리기]
|
|
//
|
|
void redraw(EventHandler* eventHandler)
|
|
{
|
|
// --- Phase 1: 상태 업데이트 및 리소스 준비 ---
|
|
// 이 단계에서는 그리기 전에 필요한 모든 상태를 확인하고, 변경이 있다면 관련 리소스를 준비합니다.
|
|
|
|
|
|
|
|
// EventHandler에 저장된 카메라의 내부 크기를 가져옵니다.
|
|
int nCameraWidth = (int)eventHandler->camera().windowSize().width;
|
|
int nCameraHeight = (int)eventHandler->camera().windowSize().height;
|
|
|
|
// 카메라의 내부 크기가 JavaScript에서 받은 최신 전역 변수 값과 다른지 비교합니다.
|
|
if (nCameraWidth != g_nWindowSizeWidth || nCameraHeight != g_nWindowSizeHeight)
|
|
{
|
|
// 다르다면, 최신 값으로 카메라와 뷰포트를 업데이트합니다.
|
|
glViewport(0, 0, g_nWindowSizeWidth, g_nWindowSizeHeight);
|
|
eventHandler->camera().setWindowSize(g_nWindowSizeWidth, g_nWindowSizeHeight);
|
|
|
|
printf("redraw: Window size CORRECTED to (%d, %d)\n", g_nWindowSizeWidth, g_nWindowSizeHeight);
|
|
|
|
// 창 크기가 바뀔 때 지오메트리(정점 좌표 등)를 다시 계산합니다.
|
|
if (g_nColorType == 0) {
|
|
if (g_nBitsAllocated == 8) {
|
|
initGeometry(g_nPaletteEntries > 0 ? g_shaderProgramGray8Palette : g_shaderProgramGray8);
|
|
} else {
|
|
initGeometry(g_shaderProgramGray16);
|
|
}
|
|
} else if (g_nColorType == 1) {
|
|
initGeometry(g_shaderProgramRGB);
|
|
}
|
|
}
|
|
|
|
// 1-3. DICOM 데이터 변경 처리 (중요: g_bChange 플래그)
|
|
if (g_bChange == true)
|
|
{
|
|
printf("redraw: DICOM data changed (g_bChange=true)\n");
|
|
// 데이터가 바뀌었을 때만 텍스처와 지오메트리를 완전히 새로 생성합니다.
|
|
if (g_nColorType == 0) // Gray
|
|
{
|
|
if (g_nBitsAllocated == 8) {
|
|
if (g_nPaletteEntries > 0) {
|
|
initGeometry(g_shaderProgramGray8Palette);
|
|
initTextureGray8Palette();
|
|
} else {
|
|
initGeometry(g_shaderProgramGray8);
|
|
initTextureGray8();
|
|
}
|
|
} else { // 16비트 Gray
|
|
initGeometry(g_shaderProgramGray16);
|
|
initTextureGray16();
|
|
}
|
|
}
|
|
else if (g_nColorType == 1) // RGB
|
|
{
|
|
initGeometry(g_shaderProgramRGB);
|
|
initTextureRGB();
|
|
}
|
|
g_bChange = false; // 리소스 생성이 끝났으므로 플래그를 내립니다.
|
|
g_bUseFirst = true;
|
|
}
|
|
|
|
// --- Phase 2: 이번 프레임에 사용할 셰이더 프로그램 결정 ---
|
|
// 현재 상태(색상, 비트 등)에 따라 어떤 셰이더를 사용할지 선택합니다.
|
|
GLuint programToUse = 0;
|
|
if (g_nColorType == 0) { // Gray
|
|
if (g_nBitsAllocated == 8) {
|
|
programToUse = (g_nPaletteEntries > 0) ? g_shaderProgramGray8Palette : g_shaderProgramGray8;
|
|
} else {
|
|
programToUse = g_shaderProgramGray16;
|
|
}
|
|
} else if (g_nColorType == 1) { // RGB
|
|
programToUse = g_shaderProgramRGB;
|
|
}
|
|
|
|
// --- Phase 3: 렌더링 ---
|
|
// 그릴 데이터가 있고, 사용할 셰이더가 결정되었을 때만 렌더링을 수행합니다.
|
|
if (g_nTotalFrames > 0 && programToUse != 0 && g_bVisible==true)
|
|
{
|
|
// 1-1. 다중 프레임 애니메이션 처리
|
|
if (g_nTotalFrames > 1 && g_bUseFirst==true)
|
|
{
|
|
Uint32 lastUpdateTime = SDL_GetTicks();
|
|
if (lastUpdateTime - g_lastUpdateTime > g_fFrameTime)
|
|
{
|
|
UpdateFrame();
|
|
g_lastUpdateTime = lastUpdateTime;
|
|
}
|
|
}
|
|
|
|
// 3-1. ✨ 셰이더 활성화 (가장 중요한 부분!)
|
|
// 매 프레임 그리기 직전에 항상 어떤 셰이더를 쓸지 명시적으로 알려줍니다.
|
|
glUseProgram(programToUse);
|
|
|
|
// 3-2. 유니폼(Uniform) 변수 업데이트
|
|
// Pan, Zoom, Window/Level 등 프레임마다 바뀔 수 있는 값들을 셰이더로 전달합니다.
|
|
updateShader(eventHandler); // 카메라(Pan, Zoom) 정보 업데이트
|
|
|
|
if (g_nColorType == 0) { // Gray
|
|
if (g_nBitsAllocated > 8) {
|
|
updateWindowWidthLevelGray16(g_nPrevWindowWidth, g_nPrevWindowCenter);
|
|
} else {
|
|
if (g_nPaletteEntries > 0) {
|
|
updateWindowWidthLevelGray8Palette(g_nPrevWindowWidth, g_nPrevWindowCenter);
|
|
} else {
|
|
updateWindowWidthLevelGray8(g_nPrevWindowWidth, g_nPrevWindowCenter);
|
|
}
|
|
}
|
|
} else { // RGB
|
|
updateWindowWidthLevelRGB(g_nPrevWindowWidth, g_nPrevWindowCenter);
|
|
}
|
|
|
|
// 3-3. 화면 클리어 및 그리기
|
|
glClearColor(0.0, 0.0, 0.0, 1.0);
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
|
|
glDrawArrays(GL_TRIANGLES, 0, 6); // 버퍼에 있는 정점 6개를 삼각형으로 그립니다.
|
|
|
|
//printf("glDrawArrays\n");
|
|
}
|
|
else
|
|
{
|
|
// 그릴 데이터가 없을 경우, 회색 배경으로 화면을 지웁니다.
|
|
float fColor = 32.0f / 255.0f;
|
|
glClearColor(fColor, fColor, fColor, 1.0);
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
}
|
|
|
|
// --- Phase 4: 화면 스왑 ---
|
|
// 완성된 백버퍼를 화면(프론트버퍼)으로 표시합니다.
|
|
eventHandler->swapWindow();
|
|
}
|
|
|
|
|
|
void mainLoop(void* mainLoopArg)
|
|
{
|
|
EventHandler* eventHandler = (EventHandler*)mainLoopArg;
|
|
eventHandler->processEvents();
|
|
|
|
int nType = eventHandler->GetEventType();
|
|
|
|
if(g_currentMode&InteractionMode::MODE_WIDTHLEVEL)
|
|
{
|
|
//printf("nType: %d\n", nType);
|
|
if(nType==1 || nType==2)
|
|
{
|
|
float fDeltaX = eventHandler->GetDeltaX();
|
|
float fDeltaY = eventHandler->GetDeltaY();
|
|
g_nPrevWindowCenter = g_nWindowCenter+fDeltaY;
|
|
g_nPrevWindowWidth = g_nWindowWidth + fDeltaX;
|
|
}
|
|
else if(nType==3 || nType==4)
|
|
{
|
|
g_nWindowCenter = g_nPrevWindowCenter;
|
|
g_nWindowWidth = g_nPrevWindowWidth;
|
|
|
|
}
|
|
|
|
}
|
|
// Update shader if camera changed
|
|
if (eventHandler->camera().updated())
|
|
updateShader(eventHandler);
|
|
|
|
redraw(eventHandler);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* 이 함수는 Emscripten에 의해 매 프레임마다 호출될 메인 루프입니다.
|
|
*/
|
|
void main_loop(void* pMainLoopArg) {
|
|
|
|
|
|
|
|
|
|
EventHandler* pEventHandler = (EventHandler*)pMainLoopArg;
|
|
|
|
SDL_Renderer* renderer = pEventHandler->GetRenderer();
|
|
|
|
|
|
// 시간에 따라 계속 변하는 색상 값을 만듭니다 (0 ~ 255).
|
|
// SDL_GetTicks()는 SDL 초기화 후 경과된 시간을 밀리초 단위로 반환합니다.
|
|
Uint8 red_value = (SDL_GetTicks() / 10) % 255;
|
|
|
|
// 렌더링 색상을 설정합니다 (R, G, B, A).
|
|
SDL_SetRenderDrawColor(renderer, red_value, 64, 64, 255);
|
|
|
|
// 설정된 색상으로 화면 전체를 지웁니다.
|
|
SDL_RenderClear(renderer);
|
|
|
|
// 지금까지의 렌더링 명령을 화면에 실제로 표시합니다.
|
|
SDL_RenderPresent(renderer);
|
|
}
|
|
|
|
// ✨ 1. 모든 GL 리소스를 생성하는 새로운 함수를 만듭니다.
|
|
void initGLResources()
|
|
{
|
|
printf("Initializing all OpenGL resources...\n");
|
|
glGenTextures(1, &g_nTextureGray8ID);
|
|
glGenTextures(1, &g_nTextureGray16ID);
|
|
glGenTextures(1, &g_nTextureID); // RGB 텍스처용
|
|
glGenTextures(1, &g_nTextureGray8PaletteID);
|
|
glGenTextures(1, &g_nPaletteTextureID);
|
|
|
|
glGenBuffers(1, &g_vbo);
|
|
|
|
// 에러가 없는지 확인
|
|
PrintError();
|
|
}
|
|
|
|
/**
|
|
* 애플리케이션의 시작점입니다.
|
|
*/
|
|
int main(int argc, char* argv[]) {
|
|
printf("C++ main 함수 시작!\n");
|
|
|
|
g_nTotalFrames = 0;
|
|
g_bVisible = true;
|
|
|
|
OFLog::configure(OFLogger::OFF_LOG_LEVEL);
|
|
EventHandler* eventHandler = new EventHandler("dcm image viewer");
|
|
|
|
DJDecoderRegistration::registerCodecs();
|
|
DJLSDecoderRegistration::registerCodecs();
|
|
DcmRLEDecoderRegistration::registerCodecs();
|
|
|
|
std::string strLoadFile = "http://192.168.1.21:3001/0010.dcm";
|
|
//emscripten_async_wget_data(strLoadFile.c_str(), (void*)135, onLoadedData, onErrorData);
|
|
|
|
|
|
SetEventHandler(eventHandler);
|
|
initGLResources(); // 모든 텍스처와 버퍼 ID를 여기서 미리 받아옵니다.
|
|
|
|
printf("SDL 초기화 성공, 메인 루프 설정 시도...\n");
|
|
|
|
int fps = -1;
|
|
|
|
// Emscripten에게 main_loop 함수를 브라우저의 렌더링 주기에 맞춰 계속 호출하도록 등록합니다.
|
|
// 이 함수를 등록한 후, main 함수는 정상적으로 종료되어야 합니다.
|
|
//emscripten_set_main_loop(main_loop, &eventHandler, 1);
|
|
//emscripten_set_main_loop_arg(main_loop, (void*)&eventHandler, fps, true);
|
|
emscripten_set_main_loop_arg(mainLoop, (void*)eventHandler, fps, true);
|
|
|
|
// main 함수는 여기서 종료되지만, 등록된 main_loop 덕분에 프로그램은 계속 실행됩니다.
|
|
// 이 시점 이후에 자바스크립트의 onRuntimeInitialized 콜백이 호출됩니다.
|
|
|
|
//DJDecoderRegistration::cleanup();
|
|
//DJLSDecoderRegistration::cleanup();
|
|
//DcmRLEDecoderRegistration::cleanup();
|
|
|
|
return 0;
|
|
}
|