SDcmWeb/webassembly/test.cc
2025-10-12 00:17:30 +09:00

2589 lines
85 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_nBitsStored = 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;
bool g_bUseMonochrome1 = false;
//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"
"uniform bool u_useMonochrome1; \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"
" if (u_useMonochrome1) { \n"
" normalizedValue = 1.0 - normalizedValue; \n"
" } \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());
// Monochrome1 모드 사용 여부를 유니폼 변수로 전달
GLint useMonochromeLocation = glGetUniformLocation(g_shaderProgramGray16, "u_useMonochrome1");
glUniform1i(useMonochromeLocation, g_bUseMonochrome1 ? 1 : 0); // 팔레트 모드 활성화
printf("updateShader: g_bUseMonochrome1=%d\n", g_bUseMonochrome1);
}
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 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++ Error: -1 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++ Error: -2 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++ Error: -3 callback_UpdateDcmImageComplete\n");
callback_UpdateDcmImageComplete(-3);
}
return -3;
}
g_pDcmDataset->transferEnd();
*/
dcmBuffer.releaseBuffer();
// ***** [추가] 압축 해제를 미리 수행 *****
printf("[Decompression] Proactively decompressing the dataset...\n");
fflush(stdout);
error = g_pDcmDataset->chooseRepresentation(EXS_LittleEndianExplicit, nullptr);
if (error.bad()) {
fprintf(stderr, "ERROR -> Failed to decompress dataset: %s\n", error.text());
fflush(stderr);
// ... error handling ...
} else {
printf(" - Decompression successful.\n");
fflush(stdout);
}
g_nTotalFrames = 1;
g_nFrameTextureWidth = 0;
g_nFrameTextureHeight = 0;
g_nSamplesPerPixel = 1;
g_nBitsAllocated = 8;
g_nBitsStored = 8;
// Photometric Interpretation 태그를 읽어 이미지 종류를 확인합니다.
OFString photometricInterpretation;
g_pDcmDataset->findAndGetOFString(DCM_PhotometricInterpretation, photometricInterpretation);
g_photometricInterpretation = photometricInterpretation.c_str();
if(photometricInterpretation == "MONOCHROME1")
{
g_bUseMonochrome1 = true;
}
else
{
g_bUseMonochrome1 = false;
}
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;
g_pDcmDataset->findAndGetUint16(DCM_BitsStored, val);
g_nBitsStored = val;
printf("[DICOM Metacheck] Width: %d, Height: %d, SamplesPerPixel: %d, BitsAllocated: %d\n, BitsStored: %d\n",
g_nFrameTextureWidth, g_nFrameTextureHeight, g_nSamplesPerPixel, g_nBitsAllocated, g_nBitsStored);
fflush(stdout);
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;
}
printf("[Buffer Check] Calculated uncompressed frame size: %u bytes\n", g_nFrameSizeUncompressed);
fflush(stdout);
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);
}
printf("[Pixel Data Sample] First 20 bytes of g_cachedFrames:\n");
for (int j = 0; j < 20; ++j) {
// 16비트 데이터일 경우 2바이트씩 읽어야 의미가 있으므로 Uint16 포인터로 캐스팅
if (g_nBitsAllocated == 16) {
Uint16* p16 = (Uint16*)g_cachedFrames;
printf("%u ", p16[j]);
} else { // 8비트 데이터
printf("%u ", g_cachedFrames[j]);
}
}
printf("\n");
fflush(stdout);
}
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("마우스 이동: (%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 bool IsUseMonochrome1()
{
return g_bUseMonochrome1;
}
EXTERN EMSCRIPTEN_KEEPALIVE int GetCurrentFrame()
{
return g_nCurrentFrame;
}
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 GetBitsStored()
{
return g_nBitsStored;
}
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 nBitsStored, int nSamplesPerPixel,
int nDefaultWindowWidth, int nDefaultWindowCenter, int nPaletteEntries, int nColorType, bool bUseMonochrome1,
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_nBitsStored = nBitsStored;
g_bUseMonochrome1 = bUseMonochrome1;
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_nBitsStored = 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) {
if(g_nPaletteEntries>0)
{
programToUse = g_shaderProgramGray8Palette;
}
else
{
programToUse = 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 > 0 && 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;
*/
// 1. 이미지의 전체 동적 범위를 계산합니다.
// pow()는 비용이 큰 함수이므로, (1 << g_nBitsStored) 비트 쉬프트 연산을 사용하는 것이 효율적입니다.
float fullRange = static_cast<float>(1 << g_nBitsStored);
// 2. 마우스 감도 기준값을 설정합니다. (이 값을 조절하여 전체 감도를 튜닝할 수 있습니다)
const float sensitivityBase = 2048.0f;
printf("fullRange(%d): %f, sensitivityBase: %f\n", g_nBitsStored, fullRange, sensitivityBase);
// 3. 민감도 계수를 계산합니다.
float scaleFactor = fullRange / sensitivityBase;
// 4. 마우스 움직임(delta)에 계수를 곱하여 최종 변화량을 결정합니다.
float fDeltaX = eventHandler->GetDeltaX() * scaleFactor;
float fDeltaY = eventHandler->GetDeltaY() * scaleFactor;
// effectiveDeltaY: 최종적으로 적용될 Y축 변화량
float effectiveDeltaY = fDeltaY;
// 만약 반전 모드(monochrome1)가 활성화되었다면,
// 마우스 상하 움직임의 효과를 반대로 적용합니다.
if (g_bUseMonochrome1) // g_bUseMonochrome1은 C++에서 사용하는 boolean 플래그라고 가정
{
effectiveDeltaY = fDeltaY * -1.0f;
}
// g_nPrevWindow... 변수들은 float으로 변경하거나, 아래처럼 int로 캐스팅해야 할 수 있습니다.
// 변수 타입에 맞게 조절해주세요.
g_nPrevWindowCenter = static_cast<int>(g_nWindowCenter + effectiveDeltaY);
g_nPrevWindowWidth = static_cast<int>(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;
}